evrex-mcp 0.9.0 → 0.10.0
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 +24 -0
- package/dist/account.js +24 -2
- package/dist/capture.js +40 -8
- package/dist/continuity.js +24 -2
- package/dist/hook.js +24 -2
- package/dist/import.js +47 -15
- package/dist/index.js +1534 -1162
- package/dist/pretool.js +24 -2
- package/dist/tickets.js +41 -9
- package/package.json +16 -15
package/README.md
CHANGED
|
@@ -64,6 +64,30 @@ re-rendered from the transcript and marked authoritative over the summary).
|
|
|
64
64
|
| `EVREX_API_BASE_URL` | no | Point at a self-hosted evrex backend. Defaults to the hosted one. |
|
|
65
65
|
| `ANTHROPIC_MODEL` | no | Defaults to `claude-opus-5`. |
|
|
66
66
|
|
|
67
|
+
## Hosted mode
|
|
68
|
+
|
|
69
|
+
The same tools over HTTPS, for agents that run in someone else's cloud and can only attach a remote MCP server.
|
|
70
|
+
That includes Claude Tag in Slack, Cursor cloud agents, the Copilot coding agent, Devin and CI.
|
|
71
|
+
Evrex runs it at `https://api.evrex.ai/mcp`. Mint a read-only token at https://app.evrex.ai/agents and give the agent:
|
|
72
|
+
|
|
73
|
+
- `Authorization: Bearer evx_…` (required)
|
|
74
|
+
- `X-Evrex-Repo: owner/name` (optional), or the full git remote URL for a repo not on github.com. This is the repo to
|
|
75
|
+
search when a tool call names none. Without it, a call searches every repo your team has indexed. A value that names
|
|
76
|
+
no repo is refused with a 400.
|
|
77
|
+
|
|
78
|
+
Where a config reads the token from the environment, keep it in `EVREX_MCP_TOKEN`, never `EVREX_TOKEN`: on a machine
|
|
79
|
+
that captures sessions, `EVREX_TOKEN` takes precedence over the enrolled capture credential, so a read-only agent
|
|
80
|
+
token there makes every capture upload fail.
|
|
81
|
+
|
|
82
|
+
To run your own copy:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
PORT=3100 EVREX_API_BASE_URL=https://api.evrex.ai npx -y evrex-mcp serve
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
It is stateless (POST `/mcp` only) and keeps no data. Each request's token makes that request's calls to the backend.
|
|
89
|
+
`EVREX_MCP_ALLOWED_ORIGINS` (comma-separated) replaces the default list of browser origins it admits.
|
|
90
|
+
|
|
67
91
|
## No API credits needed to query
|
|
68
92
|
|
|
69
93
|
The tools return the recorded context — constraints, rejected approaches, prior
|
package/dist/account.js
CHANGED
|
@@ -3,21 +3,42 @@
|
|
|
3
3
|
// src/account.ts
|
|
4
4
|
import { statSync, openSync, readSync, closeSync } from "node:fs";
|
|
5
5
|
|
|
6
|
+
// src/request-context.ts
|
|
7
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
|
+
var storage = new AsyncLocalStorage();
|
|
9
|
+
function currentRequest() {
|
|
10
|
+
return storage.getStore();
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
// src/client.ts
|
|
7
14
|
var DEFAULT_API_BASE_URL = "https://api.evrex.ai";
|
|
8
15
|
var API_BASE_URL = (process.env.EVREX_API_BASE_URL ?? process.env.EVREX_API_URL ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
|
|
9
16
|
var EVREX_TOKEN = process.env.EVREX_TOKEN ?? process.env.EVREX_API_TOKEN ?? null;
|
|
17
|
+
function token() {
|
|
18
|
+
return currentRequest()?.token ?? EVREX_TOKEN;
|
|
19
|
+
}
|
|
10
20
|
function headers() {
|
|
11
21
|
const base = { "Content-Type": "application/json" };
|
|
12
|
-
|
|
22
|
+
const credential = token();
|
|
23
|
+
if (credential) base.Authorization = `Bearer ${credential}`;
|
|
13
24
|
return base;
|
|
14
25
|
}
|
|
15
26
|
function describeFailure(method, path, status, statusText) {
|
|
16
27
|
if (status === 401 || status === 403) {
|
|
28
|
+
if (currentRequest()) {
|
|
29
|
+
return `${method} ${path} -> ${status}: the Evrex token this connection carries was rejected, or lacks the scope this needs. Mint a read token at https://app.evrex.ai/agents.`;
|
|
30
|
+
}
|
|
17
31
|
return EVREX_TOKEN ? `${method} ${path} -> ${status}: the EVREX_TOKEN this server was started with was rejected. It may be revoked or expired.` : `${method} ${path} -> ${status}: this evrex backend requires a credential, but no EVREX_TOKEN is set for this MCP server.`;
|
|
18
32
|
}
|
|
19
33
|
return `${method} ${path} -> ${status} ${statusText}`;
|
|
20
34
|
}
|
|
35
|
+
var EvrexApiError = class extends Error {
|
|
36
|
+
constructor(message, status) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.status = status;
|
|
39
|
+
this.name = "EvrexApiError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
21
42
|
async function request(method, path, { body, absentIsAnswer } = {}) {
|
|
22
43
|
const res = await fetch(`${API_BASE_URL}${path}`, {
|
|
23
44
|
method,
|
|
@@ -25,7 +46,7 @@ async function request(method, path, { body, absentIsAnswer } = {}) {
|
|
|
25
46
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
26
47
|
});
|
|
27
48
|
if (res.status === 404 && absentIsAnswer) return null;
|
|
28
|
-
if (!res.ok) throw new
|
|
49
|
+
if (!res.ok) throw new EvrexApiError(describeFailure(method, path, res.status, res.statusText), res.status);
|
|
29
50
|
return await res.json();
|
|
30
51
|
}
|
|
31
52
|
var get = (path) => request("GET", path);
|
|
@@ -34,6 +55,7 @@ var post = (path, body) => request("POST", path, { body });
|
|
|
34
55
|
var evrexApi = {
|
|
35
56
|
baseUrl: API_BASE_URL,
|
|
36
57
|
repos: () => get("/repos"),
|
|
58
|
+
me: () => get("/auth/me"),
|
|
37
59
|
commits: (repoPath) => get(`/commits?repoPath=${encodeURIComponent(repoPath)}`),
|
|
38
60
|
// Abbreviated shas resolve server-side, so a value pasted from `git log`
|
|
39
61
|
// works here (apps/backend/src/reads/reads.service.ts#resolveSha).
|
package/dist/capture.js
CHANGED
|
@@ -3368,18 +3368,39 @@ var init_src = __esm({
|
|
|
3368
3368
|
}
|
|
3369
3369
|
});
|
|
3370
3370
|
|
|
3371
|
+
// src/request-context.ts
|
|
3372
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3373
|
+
function currentRequest() {
|
|
3374
|
+
return storage.getStore();
|
|
3375
|
+
}
|
|
3376
|
+
var storage;
|
|
3377
|
+
var init_request_context = __esm({
|
|
3378
|
+
"src/request-context.ts"() {
|
|
3379
|
+
"use strict";
|
|
3380
|
+
storage = new AsyncLocalStorage();
|
|
3381
|
+
}
|
|
3382
|
+
});
|
|
3383
|
+
|
|
3371
3384
|
// src/client.ts
|
|
3372
3385
|
var client_exports = {};
|
|
3373
3386
|
__export(client_exports, {
|
|
3387
|
+
EvrexApiError: () => EvrexApiError,
|
|
3374
3388
|
evrexApi: () => evrexApi
|
|
3375
3389
|
});
|
|
3390
|
+
function token() {
|
|
3391
|
+
return currentRequest()?.token ?? EVREX_TOKEN;
|
|
3392
|
+
}
|
|
3376
3393
|
function headers() {
|
|
3377
3394
|
const base = { "Content-Type": "application/json" };
|
|
3378
|
-
|
|
3395
|
+
const credential = token();
|
|
3396
|
+
if (credential) base.Authorization = `Bearer ${credential}`;
|
|
3379
3397
|
return base;
|
|
3380
3398
|
}
|
|
3381
3399
|
function describeFailure(method, path, status, statusText) {
|
|
3382
3400
|
if (status === 401 || status === 403) {
|
|
3401
|
+
if (currentRequest()) {
|
|
3402
|
+
return `${method} ${path} -> ${status}: the Evrex token this connection carries was rejected, or lacks the scope this needs. Mint a read token at https://app.evrex.ai/agents.`;
|
|
3403
|
+
}
|
|
3383
3404
|
return EVREX_TOKEN ? `${method} ${path} -> ${status}: the EVREX_TOKEN this server was started with was rejected. It may be revoked or expired.` : `${method} ${path} -> ${status}: this evrex backend requires a credential, but no EVREX_TOKEN is set for this MCP server.`;
|
|
3384
3405
|
}
|
|
3385
3406
|
return `${method} ${path} -> ${status} ${statusText}`;
|
|
@@ -3391,22 +3412,31 @@ async function request(method, path, { body, absentIsAnswer } = {}) {
|
|
|
3391
3412
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
3392
3413
|
});
|
|
3393
3414
|
if (res.status === 404 && absentIsAnswer) return null;
|
|
3394
|
-
if (!res.ok) throw new
|
|
3415
|
+
if (!res.ok) throw new EvrexApiError(describeFailure(method, path, res.status, res.statusText), res.status);
|
|
3395
3416
|
return await res.json();
|
|
3396
3417
|
}
|
|
3397
|
-
var DEFAULT_API_BASE_URL, API_BASE_URL, EVREX_TOKEN, get, getOrNull, post, evrexApi;
|
|
3418
|
+
var DEFAULT_API_BASE_URL, API_BASE_URL, EVREX_TOKEN, EvrexApiError, get, getOrNull, post, evrexApi;
|
|
3398
3419
|
var init_client = __esm({
|
|
3399
3420
|
"src/client.ts"() {
|
|
3400
3421
|
"use strict";
|
|
3422
|
+
init_request_context();
|
|
3401
3423
|
DEFAULT_API_BASE_URL = "https://api.evrex.ai";
|
|
3402
3424
|
API_BASE_URL = (process.env.EVREX_API_BASE_URL ?? process.env.EVREX_API_URL ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
|
|
3403
3425
|
EVREX_TOKEN = process.env.EVREX_TOKEN ?? process.env.EVREX_API_TOKEN ?? null;
|
|
3426
|
+
EvrexApiError = class extends Error {
|
|
3427
|
+
constructor(message, status) {
|
|
3428
|
+
super(message);
|
|
3429
|
+
this.status = status;
|
|
3430
|
+
this.name = "EvrexApiError";
|
|
3431
|
+
}
|
|
3432
|
+
};
|
|
3404
3433
|
get = (path) => request("GET", path);
|
|
3405
3434
|
getOrNull = (path) => request("GET", path, { absentIsAnswer: true });
|
|
3406
3435
|
post = (path, body) => request("POST", path, { body });
|
|
3407
3436
|
evrexApi = {
|
|
3408
3437
|
baseUrl: API_BASE_URL,
|
|
3409
3438
|
repos: () => get("/repos"),
|
|
3439
|
+
me: () => get("/auth/me"),
|
|
3410
3440
|
commits: (repoPath) => get(`/commits?repoPath=${encodeURIComponent(repoPath)}`),
|
|
3411
3441
|
// Abbreviated shas resolve server-side, so a value pasted from `git log`
|
|
3412
3442
|
// works here (apps/backend/src/reads/reads.service.ts#resolveSha).
|
|
@@ -3500,11 +3530,13 @@ evrex will not fall back to writing the credential in a plain file.`
|
|
|
3500
3530
|
if (!await this.available()) throw new NoKeychainError(this.runner.platform);
|
|
3501
3531
|
switch (this.runner.platform) {
|
|
3502
3532
|
case "darwin": {
|
|
3533
|
+
if (/["\\\r\n]/.test(secret)) {
|
|
3534
|
+
throw new Error("Keychain write refused: the credential has a character `security -i` cannot carry.");
|
|
3535
|
+
}
|
|
3503
3536
|
const result = await this.runner.run(
|
|
3504
3537
|
"security",
|
|
3505
|
-
["
|
|
3506
|
-
|
|
3507
|
-
${secret}
|
|
3538
|
+
["-i"],
|
|
3539
|
+
`add-generic-password -a "${ACCOUNT}" -s "${SERVICE}" -U -w "${secret}"
|
|
3508
3540
|
`
|
|
3509
3541
|
);
|
|
3510
3542
|
if (result.code !== 0) throw new Error(`Keychain write failed: ${result.stderr.trim()}`);
|
|
@@ -3860,8 +3892,8 @@ async function main() {
|
|
|
3860
3892
|
return;
|
|
3861
3893
|
}
|
|
3862
3894
|
const { evrexApi: evrexApi2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3863
|
-
let
|
|
3864
|
-
const credential = () =>
|
|
3895
|
+
let token2;
|
|
3896
|
+
const credential = () => token2 ??= captureToken(process.env, async () => {
|
|
3865
3897
|
const { CredentialStore: CredentialStore2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
|
|
3866
3898
|
return new CredentialStore2().retrieve();
|
|
3867
3899
|
});
|
package/dist/continuity.js
CHANGED
|
@@ -5,21 +5,42 @@ import { execFileSync as execFileSync2 } from "node:child_process";
|
|
|
5
5
|
import { closeSync, openSync, readSync, statSync } from "node:fs";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
|
|
8
|
+
// src/request-context.ts
|
|
9
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
10
|
+
var storage = new AsyncLocalStorage();
|
|
11
|
+
function currentRequest() {
|
|
12
|
+
return storage.getStore();
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
// src/client.ts
|
|
9
16
|
var DEFAULT_API_BASE_URL = "https://api.evrex.ai";
|
|
10
17
|
var API_BASE_URL = (process.env.EVREX_API_BASE_URL ?? process.env.EVREX_API_URL ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
|
|
11
18
|
var EVREX_TOKEN = process.env.EVREX_TOKEN ?? process.env.EVREX_API_TOKEN ?? null;
|
|
19
|
+
function token() {
|
|
20
|
+
return currentRequest()?.token ?? EVREX_TOKEN;
|
|
21
|
+
}
|
|
12
22
|
function headers() {
|
|
13
23
|
const base = { "Content-Type": "application/json" };
|
|
14
|
-
|
|
24
|
+
const credential = token();
|
|
25
|
+
if (credential) base.Authorization = `Bearer ${credential}`;
|
|
15
26
|
return base;
|
|
16
27
|
}
|
|
17
28
|
function describeFailure(method, path, status, statusText) {
|
|
18
29
|
if (status === 401 || status === 403) {
|
|
30
|
+
if (currentRequest()) {
|
|
31
|
+
return `${method} ${path} -> ${status}: the Evrex token this connection carries was rejected, or lacks the scope this needs. Mint a read token at https://app.evrex.ai/agents.`;
|
|
32
|
+
}
|
|
19
33
|
return EVREX_TOKEN ? `${method} ${path} -> ${status}: the EVREX_TOKEN this server was started with was rejected. It may be revoked or expired.` : `${method} ${path} -> ${status}: this evrex backend requires a credential, but no EVREX_TOKEN is set for this MCP server.`;
|
|
20
34
|
}
|
|
21
35
|
return `${method} ${path} -> ${status} ${statusText}`;
|
|
22
36
|
}
|
|
37
|
+
var EvrexApiError = class extends Error {
|
|
38
|
+
constructor(message, status) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.status = status;
|
|
41
|
+
this.name = "EvrexApiError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
23
44
|
async function request(method, path, { body, absentIsAnswer } = {}) {
|
|
24
45
|
const res = await fetch(`${API_BASE_URL}${path}`, {
|
|
25
46
|
method,
|
|
@@ -27,7 +48,7 @@ async function request(method, path, { body, absentIsAnswer } = {}) {
|
|
|
27
48
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
28
49
|
});
|
|
29
50
|
if (res.status === 404 && absentIsAnswer) return null;
|
|
30
|
-
if (!res.ok) throw new
|
|
51
|
+
if (!res.ok) throw new EvrexApiError(describeFailure(method, path, res.status, res.statusText), res.status);
|
|
31
52
|
return await res.json();
|
|
32
53
|
}
|
|
33
54
|
var get = (path) => request("GET", path);
|
|
@@ -36,6 +57,7 @@ var post = (path, body) => request("POST", path, { body });
|
|
|
36
57
|
var evrexApi = {
|
|
37
58
|
baseUrl: API_BASE_URL,
|
|
38
59
|
repos: () => get("/repos"),
|
|
60
|
+
me: () => get("/auth/me"),
|
|
39
61
|
commits: (repoPath) => get(`/commits?repoPath=${encodeURIComponent(repoPath)}`),
|
|
40
62
|
// Abbreviated shas resolve server-side, so a value pasted from `git log`
|
|
41
63
|
// works here (apps/backend/src/reads/reads.service.ts#resolveSha).
|
package/dist/hook.js
CHANGED
|
@@ -3,21 +3,42 @@
|
|
|
3
3
|
// src/hook.ts
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
|
|
6
|
+
// src/request-context.ts
|
|
7
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
|
+
var storage = new AsyncLocalStorage();
|
|
9
|
+
function currentRequest() {
|
|
10
|
+
return storage.getStore();
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
// src/client.ts
|
|
7
14
|
var DEFAULT_API_BASE_URL = "https://api.evrex.ai";
|
|
8
15
|
var API_BASE_URL = (process.env.EVREX_API_BASE_URL ?? process.env.EVREX_API_URL ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
|
|
9
16
|
var EVREX_TOKEN = process.env.EVREX_TOKEN ?? process.env.EVREX_API_TOKEN ?? null;
|
|
17
|
+
function token() {
|
|
18
|
+
return currentRequest()?.token ?? EVREX_TOKEN;
|
|
19
|
+
}
|
|
10
20
|
function headers() {
|
|
11
21
|
const base = { "Content-Type": "application/json" };
|
|
12
|
-
|
|
22
|
+
const credential = token();
|
|
23
|
+
if (credential) base.Authorization = `Bearer ${credential}`;
|
|
13
24
|
return base;
|
|
14
25
|
}
|
|
15
26
|
function describeFailure(method, path, status, statusText) {
|
|
16
27
|
if (status === 401 || status === 403) {
|
|
28
|
+
if (currentRequest()) {
|
|
29
|
+
return `${method} ${path} -> ${status}: the Evrex token this connection carries was rejected, or lacks the scope this needs. Mint a read token at https://app.evrex.ai/agents.`;
|
|
30
|
+
}
|
|
17
31
|
return EVREX_TOKEN ? `${method} ${path} -> ${status}: the EVREX_TOKEN this server was started with was rejected. It may be revoked or expired.` : `${method} ${path} -> ${status}: this evrex backend requires a credential, but no EVREX_TOKEN is set for this MCP server.`;
|
|
18
32
|
}
|
|
19
33
|
return `${method} ${path} -> ${status} ${statusText}`;
|
|
20
34
|
}
|
|
35
|
+
var EvrexApiError = class extends Error {
|
|
36
|
+
constructor(message, status) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.status = status;
|
|
39
|
+
this.name = "EvrexApiError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
21
42
|
async function request(method, path, { body, absentIsAnswer } = {}) {
|
|
22
43
|
const res = await fetch(`${API_BASE_URL}${path}`, {
|
|
23
44
|
method,
|
|
@@ -25,7 +46,7 @@ async function request(method, path, { body, absentIsAnswer } = {}) {
|
|
|
25
46
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
26
47
|
});
|
|
27
48
|
if (res.status === 404 && absentIsAnswer) return null;
|
|
28
|
-
if (!res.ok) throw new
|
|
49
|
+
if (!res.ok) throw new EvrexApiError(describeFailure(method, path, res.status, res.statusText), res.status);
|
|
29
50
|
return await res.json();
|
|
30
51
|
}
|
|
31
52
|
var get = (path) => request("GET", path);
|
|
@@ -34,6 +55,7 @@ var post = (path, body) => request("POST", path, { body });
|
|
|
34
55
|
var evrexApi = {
|
|
35
56
|
baseUrl: API_BASE_URL,
|
|
36
57
|
repos: () => get("/repos"),
|
|
58
|
+
me: () => get("/auth/me"),
|
|
37
59
|
commits: (repoPath) => get(`/commits?repoPath=${encodeURIComponent(repoPath)}`),
|
|
38
60
|
// Abbreviated shas resolve server-side, so a value pasted from `git log`
|
|
39
61
|
// works here (apps/backend/src/reads/reads.service.ts#resolveSha).
|
package/dist/import.js
CHANGED
|
@@ -3368,18 +3368,39 @@ var init_src = __esm({
|
|
|
3368
3368
|
}
|
|
3369
3369
|
});
|
|
3370
3370
|
|
|
3371
|
+
// src/request-context.ts
|
|
3372
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3373
|
+
function currentRequest() {
|
|
3374
|
+
return storage.getStore();
|
|
3375
|
+
}
|
|
3376
|
+
var storage;
|
|
3377
|
+
var init_request_context = __esm({
|
|
3378
|
+
"src/request-context.ts"() {
|
|
3379
|
+
"use strict";
|
|
3380
|
+
storage = new AsyncLocalStorage();
|
|
3381
|
+
}
|
|
3382
|
+
});
|
|
3383
|
+
|
|
3371
3384
|
// src/client.ts
|
|
3372
3385
|
var client_exports = {};
|
|
3373
3386
|
__export(client_exports, {
|
|
3387
|
+
EvrexApiError: () => EvrexApiError,
|
|
3374
3388
|
evrexApi: () => evrexApi
|
|
3375
3389
|
});
|
|
3390
|
+
function token() {
|
|
3391
|
+
return currentRequest()?.token ?? EVREX_TOKEN;
|
|
3392
|
+
}
|
|
3376
3393
|
function headers() {
|
|
3377
3394
|
const base = { "Content-Type": "application/json" };
|
|
3378
|
-
|
|
3395
|
+
const credential = token();
|
|
3396
|
+
if (credential) base.Authorization = `Bearer ${credential}`;
|
|
3379
3397
|
return base;
|
|
3380
3398
|
}
|
|
3381
3399
|
function describeFailure(method, path, status, statusText) {
|
|
3382
3400
|
if (status === 401 || status === 403) {
|
|
3401
|
+
if (currentRequest()) {
|
|
3402
|
+
return `${method} ${path} -> ${status}: the Evrex token this connection carries was rejected, or lacks the scope this needs. Mint a read token at https://app.evrex.ai/agents.`;
|
|
3403
|
+
}
|
|
3383
3404
|
return EVREX_TOKEN ? `${method} ${path} -> ${status}: the EVREX_TOKEN this server was started with was rejected. It may be revoked or expired.` : `${method} ${path} -> ${status}: this evrex backend requires a credential, but no EVREX_TOKEN is set for this MCP server.`;
|
|
3384
3405
|
}
|
|
3385
3406
|
return `${method} ${path} -> ${status} ${statusText}`;
|
|
@@ -3391,22 +3412,31 @@ async function request(method, path, { body, absentIsAnswer } = {}) {
|
|
|
3391
3412
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
3392
3413
|
});
|
|
3393
3414
|
if (res.status === 404 && absentIsAnswer) return null;
|
|
3394
|
-
if (!res.ok) throw new
|
|
3415
|
+
if (!res.ok) throw new EvrexApiError(describeFailure(method, path, res.status, res.statusText), res.status);
|
|
3395
3416
|
return await res.json();
|
|
3396
3417
|
}
|
|
3397
|
-
var DEFAULT_API_BASE_URL, API_BASE_URL, EVREX_TOKEN, get, getOrNull, post, evrexApi;
|
|
3418
|
+
var DEFAULT_API_BASE_URL, API_BASE_URL, EVREX_TOKEN, EvrexApiError, get, getOrNull, post, evrexApi;
|
|
3398
3419
|
var init_client = __esm({
|
|
3399
3420
|
"src/client.ts"() {
|
|
3400
3421
|
"use strict";
|
|
3422
|
+
init_request_context();
|
|
3401
3423
|
DEFAULT_API_BASE_URL = "https://api.evrex.ai";
|
|
3402
3424
|
API_BASE_URL = (process.env.EVREX_API_BASE_URL ?? process.env.EVREX_API_URL ?? DEFAULT_API_BASE_URL).replace(/\/+$/, "");
|
|
3403
3425
|
EVREX_TOKEN = process.env.EVREX_TOKEN ?? process.env.EVREX_API_TOKEN ?? null;
|
|
3426
|
+
EvrexApiError = class extends Error {
|
|
3427
|
+
constructor(message, status) {
|
|
3428
|
+
super(message);
|
|
3429
|
+
this.status = status;
|
|
3430
|
+
this.name = "EvrexApiError";
|
|
3431
|
+
}
|
|
3432
|
+
};
|
|
3404
3433
|
get = (path) => request("GET", path);
|
|
3405
3434
|
getOrNull = (path) => request("GET", path, { absentIsAnswer: true });
|
|
3406
3435
|
post = (path, body) => request("POST", path, { body });
|
|
3407
3436
|
evrexApi = {
|
|
3408
3437
|
baseUrl: API_BASE_URL,
|
|
3409
3438
|
repos: () => get("/repos"),
|
|
3439
|
+
me: () => get("/auth/me"),
|
|
3410
3440
|
commits: (repoPath) => get(`/commits?repoPath=${encodeURIComponent(repoPath)}`),
|
|
3411
3441
|
// Abbreviated shas resolve server-side, so a value pasted from `git log`
|
|
3412
3442
|
// works here (apps/backend/src/reads/reads.service.ts#resolveSha).
|
|
@@ -3500,11 +3530,13 @@ evrex will not fall back to writing the credential in a plain file.`
|
|
|
3500
3530
|
if (!await this.available()) throw new NoKeychainError(this.runner.platform);
|
|
3501
3531
|
switch (this.runner.platform) {
|
|
3502
3532
|
case "darwin": {
|
|
3533
|
+
if (/["\\\r\n]/.test(secret)) {
|
|
3534
|
+
throw new Error("Keychain write refused: the credential has a character `security -i` cannot carry.");
|
|
3535
|
+
}
|
|
3503
3536
|
const result = await this.runner.run(
|
|
3504
3537
|
"security",
|
|
3505
|
-
["
|
|
3506
|
-
|
|
3507
|
-
${secret}
|
|
3538
|
+
["-i"],
|
|
3539
|
+
`add-generic-password -a "${ACCOUNT}" -s "${SERVICE}" -U -w "${secret}"
|
|
3508
3540
|
`
|
|
3509
3541
|
);
|
|
3510
3542
|
if (result.code !== 0) throw new Error(`Keychain write failed: ${result.stderr.trim()}`);
|
|
@@ -3778,8 +3810,8 @@ async function durability(target, argv) {
|
|
|
3778
3810
|
};
|
|
3779
3811
|
const minAgeDays = flag("--min-age-days", 30);
|
|
3780
3812
|
const limit = flag("--limit", 300);
|
|
3781
|
-
const
|
|
3782
|
-
if (!
|
|
3813
|
+
const token2 = process.env.EVREX_TOKEN ?? await new (await Promise.resolve().then(() => (init_credential_store(), credential_store_exports))).CredentialStore().retrieve().catch(() => null);
|
|
3814
|
+
if (!token2) {
|
|
3783
3815
|
console.error("evrex: this machine is not enrolled.\n Run `npx -y evrex-mcp enrol` first, or set EVREX_TOKEN.");
|
|
3784
3816
|
process.exit(1);
|
|
3785
3817
|
}
|
|
@@ -3796,7 +3828,7 @@ async function durability(target, argv) {
|
|
|
3796
3828
|
}
|
|
3797
3829
|
const res = await fetch(`${evrexApi2.baseUrl}/ingest/commits/durability`, {
|
|
3798
3830
|
method: "POST",
|
|
3799
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${
|
|
3831
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token2}` },
|
|
3800
3832
|
body: JSON.stringify({ repoId, measurements })
|
|
3801
3833
|
});
|
|
3802
3834
|
if (!res.ok) {
|
|
@@ -3812,8 +3844,8 @@ async function parents(target) {
|
|
|
3812
3844
|
const { deriveRepoId: deriveRepoId2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
3813
3845
|
const { execFileSync: execFileSync5 } = await import("node:child_process");
|
|
3814
3846
|
const { evrexApi: evrexApi2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3815
|
-
const
|
|
3816
|
-
if (!
|
|
3847
|
+
const token2 = process.env.EVREX_TOKEN ?? await new (await Promise.resolve().then(() => (init_credential_store(), credential_store_exports))).CredentialStore().retrieve().catch(() => null);
|
|
3848
|
+
if (!token2) {
|
|
3817
3849
|
console.error("evrex: this machine is not enrolled.\n Run `npx -y evrex-mcp enrol` first, or set EVREX_TOKEN.");
|
|
3818
3850
|
process.exit(1);
|
|
3819
3851
|
}
|
|
@@ -3828,7 +3860,7 @@ async function parents(target) {
|
|
|
3828
3860
|
for (let i = 0; i < rows.length; i += 500) {
|
|
3829
3861
|
const res = await fetch(`${evrexApi2.baseUrl}/ingest/commits/parents`, {
|
|
3830
3862
|
method: "POST",
|
|
3831
|
-
headers: { "content-type": "application/json", authorization: `Bearer ${
|
|
3863
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token2}` },
|
|
3832
3864
|
body: JSON.stringify({ repoId, commits: rows.slice(i, i + 500) })
|
|
3833
3865
|
});
|
|
3834
3866
|
if (!res.ok) {
|
|
@@ -3853,8 +3885,8 @@ async function main() {
|
|
|
3853
3885
|
const target = resolve(process.argv[3] ?? process.cwd());
|
|
3854
3886
|
const { evrexApi: evrexApi2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
3855
3887
|
const { CredentialStore: CredentialStore2 } = await Promise.resolve().then(() => (init_credential_store(), credential_store_exports));
|
|
3856
|
-
const
|
|
3857
|
-
if (!
|
|
3888
|
+
const token2 = process.env.EVREX_TOKEN ?? await new CredentialStore2().retrieve().catch(() => null);
|
|
3889
|
+
if (!token2) {
|
|
3858
3890
|
console.error(
|
|
3859
3891
|
"evrex: this machine is not enrolled.\n Run `npx -y evrex-mcp enrol` first, or set EVREX_TOKEN."
|
|
3860
3892
|
);
|
|
@@ -3873,7 +3905,7 @@ async function main() {
|
|
|
3873
3905
|
method: "POST",
|
|
3874
3906
|
headers: {
|
|
3875
3907
|
"content-type": "application/json",
|
|
3876
|
-
authorization: `Bearer ${
|
|
3908
|
+
authorization: `Bearer ${token2}`
|
|
3877
3909
|
},
|
|
3878
3910
|
body: JSON.stringify(body)
|
|
3879
3911
|
});
|