@elvix.is/sdk 0.3.2 → 0.4.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 +15 -1
- package/dist/cli/doctor.d.ts +21 -0
- package/dist/cli/doctor.js +78 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +61 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -126,7 +126,7 @@ elvix ships first-class agent support. Three surfaces:
|
|
|
126
126
|
"mcpServers": {
|
|
127
127
|
"elvix": {
|
|
128
128
|
"command": "npx",
|
|
129
|
-
"args": ["-y", "-p", "@elvix.is/sdk", "elvix
|
|
129
|
+
"args": ["-y", "-p", "@elvix.is/sdk", "elvix", "mcp"],
|
|
130
130
|
"env": { "ELVIX_API_KEY": "eak_..." }
|
|
131
131
|
}
|
|
132
132
|
}
|
|
@@ -135,6 +135,20 @@ elvix ships first-class agent support. Three surfaces:
|
|
|
135
135
|
|
|
136
136
|
Read-only by default. `--admin` opts in to mutation tools. Never logs the bearer token.
|
|
137
137
|
|
|
138
|
+
## CLI
|
|
139
|
+
|
|
140
|
+
The package ships an `elvix` command:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Diagnose an integration — base URL, clientId, verify endpoint, API key.
|
|
144
|
+
ELVIX_CLIENT_ID=client_… npx -p @elvix.is/sdk elvix doctor
|
|
145
|
+
|
|
146
|
+
# Launch the MCP server on stdio.
|
|
147
|
+
ELVIX_API_KEY=eak_… npx -p @elvix.is/sdk elvix mcp
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`elvix doctor` prints a green/red checklist so "why isn't elvix working" is a two-second answer. (`elvix-mcp` is kept as an alias for `elvix mcp`.)
|
|
151
|
+
|
|
138
152
|
Full agent guide: <https://elvix.is/docs/agents>
|
|
139
153
|
|
|
140
154
|
## Components
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `elvix doctor` — diagnose an elvix integration from the terminal.
|
|
3
|
+
*
|
|
4
|
+
* Runs a green/red checklist so a developer (or their agent) can
|
|
5
|
+
* answer "why isn't elvix working" in two seconds instead of a support
|
|
6
|
+
* email. Read-only; never mutates anything.
|
|
7
|
+
*
|
|
8
|
+
* Checks:
|
|
9
|
+
* - base URL reachable (GET /llms.txt)
|
|
10
|
+
* - clientId resolves (GET /api/v1/bootstrap/<clientId> → 200)
|
|
11
|
+
* - verify endpoint live (POST /api/v1/verify with a dummy token → 401)
|
|
12
|
+
* - ELVIX_API_KEY present (informational)
|
|
13
|
+
*
|
|
14
|
+
* Inputs (env or flags):
|
|
15
|
+
* ELVIX_BASE_URL default https://elvix.is (--base-url=)
|
|
16
|
+
* ELVIX_CLIENT_ID the app's public clientId (--client-id=)
|
|
17
|
+
* ELVIX_API_KEY server API key (presence only)
|
|
18
|
+
*/
|
|
19
|
+
declare function runDoctor(): Promise<number>;
|
|
20
|
+
|
|
21
|
+
export { runDoctor };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/cli/doctor.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://elvix.is";
|
|
3
|
+
function flag(name) {
|
|
4
|
+
const hit = process.argv.find((a) => a.startsWith(`--${name}=`));
|
|
5
|
+
return hit?.split("=").slice(1).join("=");
|
|
6
|
+
}
|
|
7
|
+
async function timed(fn, timeoutMs = 8e3) {
|
|
8
|
+
const ctrl = new AbortController();
|
|
9
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
10
|
+
try {
|
|
11
|
+
return await fn();
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
} finally {
|
|
15
|
+
clearTimeout(t);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function runDoctor() {
|
|
19
|
+
const baseUrl = flag("base-url") ?? process.env.ELVIX_BASE_URL ?? DEFAULT_BASE_URL;
|
|
20
|
+
const clientId = flag("client-id") ?? process.env.ELVIX_CLIENT_ID;
|
|
21
|
+
const apiKey = process.env.ELVIX_API_KEY;
|
|
22
|
+
const checks = [];
|
|
23
|
+
const llms = await timed(() => fetch(`${baseUrl}/llms.txt`));
|
|
24
|
+
checks.push({
|
|
25
|
+
label: `Base URL reachable (${baseUrl})`,
|
|
26
|
+
ok: Boolean(llms?.ok),
|
|
27
|
+
detail: llms ? `HTTP ${llms.status}` : "no response / timeout"
|
|
28
|
+
});
|
|
29
|
+
if (clientId) {
|
|
30
|
+
const boot = await timed(
|
|
31
|
+
() => fetch(`${baseUrl}/api/v1/bootstrap/${encodeURIComponent(clientId)}`)
|
|
32
|
+
);
|
|
33
|
+
checks.push({
|
|
34
|
+
label: `clientId resolves (${clientId})`,
|
|
35
|
+
ok: Boolean(boot?.ok),
|
|
36
|
+
detail: boot ? boot.ok ? `HTTP ${boot.status}` : `HTTP ${boot.status} \u2014 wrong clientId, or app is draft/deleted` : "no response / timeout"
|
|
37
|
+
});
|
|
38
|
+
} else {
|
|
39
|
+
checks.push({
|
|
40
|
+
label: "clientId resolves",
|
|
41
|
+
ok: false,
|
|
42
|
+
detail: "skipped \u2014 pass --client-id=<id> or set ELVIX_CLIENT_ID"
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const verify = await timed(
|
|
46
|
+
() => fetch(`${baseUrl}/api/v1/verify`, {
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: { "content-type": "application/json" },
|
|
49
|
+
body: JSON.stringify({ token: "doctor-probe" })
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
checks.push({
|
|
53
|
+
label: "Verify endpoint live (POST /api/v1/verify)",
|
|
54
|
+
ok: verify?.status === 401,
|
|
55
|
+
detail: verify ? `HTTP ${verify.status} (401 expected)` : "no response / timeout"
|
|
56
|
+
});
|
|
57
|
+
checks.push({
|
|
58
|
+
label: "ELVIX_API_KEY present",
|
|
59
|
+
ok: Boolean(apiKey),
|
|
60
|
+
detail: apiKey ? `set (${apiKey.slice(0, 8)}\u2026)` : "not set \u2014 server-side verify will fail"
|
|
61
|
+
});
|
|
62
|
+
let criticalFail = false;
|
|
63
|
+
process.stdout.write("\nelvix doctor\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
64
|
+
for (const c of checks) {
|
|
65
|
+
const mark = c.ok ? "\u2713" : "\u2717";
|
|
66
|
+
process.stdout.write(` ${mark} ${c.label}
|
|
67
|
+
${c.detail}
|
|
68
|
+
`);
|
|
69
|
+
}
|
|
70
|
+
if (!checks[0]?.ok || !checks[2]?.ok) criticalFail = true;
|
|
71
|
+
process.stdout.write(
|
|
72
|
+
criticalFail ? "\nResult: elvix is NOT reachable from here. Check network / base URL.\n" : "\nResult: elvix is reachable. Warnings above (if any) are config hints.\n"
|
|
73
|
+
);
|
|
74
|
+
return criticalFail ? 1 : 0;
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
runDoctor
|
|
78
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/index.ts
|
|
4
|
+
var HELP = `elvix \u2014 @elvix.is/sdk CLI
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
elvix mcp [--admin] [--base-url=<url>]
|
|
8
|
+
Launch the elvix MCP server on stdio. Reads ELVIX_API_KEY from
|
|
9
|
+
the environment. Read-only by default; --admin enables mutation
|
|
10
|
+
tools (the server still enforces the admin scope on the key).
|
|
11
|
+
|
|
12
|
+
elvix doctor [--client-id=<id>] [--base-url=<url>]
|
|
13
|
+
Diagnose an integration: base URL reachability, clientId
|
|
14
|
+
resolution, verify-endpoint liveness, API-key presence.
|
|
15
|
+
Reads ELVIX_CLIENT_ID / ELVIX_API_KEY / ELVIX_BASE_URL.
|
|
16
|
+
|
|
17
|
+
elvix help
|
|
18
|
+
Show this message.
|
|
19
|
+
|
|
20
|
+
Docs: https://elvix.is/docs/agents`;
|
|
21
|
+
async function main() {
|
|
22
|
+
const sub = process.argv[2];
|
|
23
|
+
switch (sub) {
|
|
24
|
+
case "mcp": {
|
|
25
|
+
const { createElvixMcpServer } = await import("../mcp/index.js");
|
|
26
|
+
const apiKey = process.env.ELVIX_API_KEY;
|
|
27
|
+
if (!apiKey) {
|
|
28
|
+
process.stderr.write("ELVIX_API_KEY environment variable is required.\n");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const admin = process.argv.includes("--admin");
|
|
32
|
+
const baseUrl = process.argv.find((a) => a.startsWith("--base-url="))?.split("=")[1];
|
|
33
|
+
const { connectStdio } = await createElvixMcpServer({ apiKey, readonly: !admin, baseUrl });
|
|
34
|
+
await connectStdio();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
case "doctor": {
|
|
38
|
+
const { runDoctor } = await import("./doctor.js");
|
|
39
|
+
process.exit(await runDoctor());
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case "help":
|
|
43
|
+
case "--help":
|
|
44
|
+
case "-h":
|
|
45
|
+
case void 0:
|
|
46
|
+
process.stdout.write(`${HELP}
|
|
47
|
+
`);
|
|
48
|
+
return;
|
|
49
|
+
default:
|
|
50
|
+
process.stderr.write(`Unknown command: ${sub}
|
|
51
|
+
|
|
52
|
+
${HELP}
|
|
53
|
+
`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
main().catch((e) => {
|
|
58
|
+
process.stderr.write(`elvix: ${e instanceof Error ? e.message : String(e)}
|
|
59
|
+
`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elvix.is/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Official elvix SDK. Drop-in React components, server helpers, and an MCP server so AI coding agents integrate elvix on the first try.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://elvix.is",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"main": "./dist/index.js",
|
|
22
22
|
"types": "./dist/index.d.ts",
|
|
23
23
|
"bin": {
|
|
24
|
+
"elvix": "./dist/cli/index.js",
|
|
24
25
|
"elvix-mcp": "./dist/mcp/bin.js"
|
|
25
26
|
},
|
|
26
27
|
"exports": {
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
},
|
|
48
49
|
"files": ["dist", "README.md", "LICENSE"],
|
|
49
50
|
"scripts": {
|
|
50
|
-
"build": "tsup src/index.ts src/react.ts src/server.ts src/types.ts src/mcp/index.ts src/mcp/bin.ts --format esm --dts --clean --external react --external next",
|
|
51
|
+
"build": "tsup src/index.ts src/react.ts src/server.ts src/types.ts src/mcp/index.ts src/mcp/bin.ts src/cli/index.ts src/cli/doctor.ts --format esm --dts --clean --external react --external next",
|
|
51
52
|
"typecheck": "tsc --noEmit",
|
|
52
53
|
"test": "vitest run"
|
|
53
54
|
},
|