@elvix.is/sdk 0.3.1 → 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 +18 -3
- package/dist/{chunk-22IQNPXM.js → chunk-SSZNTXQK.js} +9 -4
- 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/dist/mcp/bin.js +1 -1
- package/dist/mcp/index.js +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -125,8 +125,8 @@ elvix ships first-class agent support. Three surfaces:
|
|
|
125
125
|
{
|
|
126
126
|
"mcpServers": {
|
|
127
127
|
"elvix": {
|
|
128
|
-
"command": "
|
|
129
|
-
"args": ["@elvix.is/sdk", "elvix
|
|
128
|
+
"command": "npx",
|
|
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
|
|
@@ -142,9 +156,10 @@ Full agent guide: <https://elvix.is/docs/agents>
|
|
|
142
156
|
Every `<Elvix*>` component the SDK ships. Drop-in React, brand chord from `<ElvixProvider>`, no prop drilling.
|
|
143
157
|
|
|
144
158
|
- Primitives: `ElvixCard`, `ElvixProvider`
|
|
145
|
-
- Sign-in: `ElvixSignIn
|
|
159
|
+
- Sign-in: `ElvixSignIn`
|
|
146
160
|
- Identity: `ElvixUsername`, `ElvixIdentityForm`, `ElvixAvatar`, `ElvixBanner`, `ElvixRegion`, `ElvixLanguages`
|
|
147
161
|
- Account: `ElvixAddressBook`, `ElvixLegalEntities`, `ElvixSessions`, `ElvixExport`, `ElvixDeactivate`, `ElvixLeave`
|
|
162
|
+
- Hooks: `useElvixApp()`, `useElvixContext()`
|
|
148
163
|
|
|
149
164
|
Full catalog with previews: <https://elvix.is/docs/components>
|
|
150
165
|
|
|
@@ -7,6 +7,11 @@ import {
|
|
|
7
7
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
8
|
var DEFAULT_BASE_URL = "https://elvix.is";
|
|
9
9
|
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
10
|
+
function splitEndpoint(endpoint) {
|
|
11
|
+
const idx = endpoint.indexOf(" ");
|
|
12
|
+
if (idx === -1) return { method: "GET", path: endpoint };
|
|
13
|
+
return { method: endpoint.slice(0, idx), path: endpoint.slice(idx + 1) };
|
|
14
|
+
}
|
|
10
15
|
function toolName(method, path) {
|
|
11
16
|
return `${method.toLowerCase()}_${path.replace(/^\/api\//, "").replace(/[\/{}]+/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "")}`;
|
|
12
17
|
}
|
|
@@ -15,9 +20,9 @@ async function createElvixMcpServer(opts) {
|
|
|
15
20
|
const readonly = opts.readonly ?? true;
|
|
16
21
|
const manifestRes = await fetch(`${baseUrl}/openapi.roles.json`);
|
|
17
22
|
const manifest = await manifestRes.json();
|
|
18
|
-
const tools = manifest.
|
|
19
|
-
name: toolName(
|
|
20
|
-
description: `${
|
|
23
|
+
const tools = manifest.filter((e) => e.role === "api").map((e) => ({ entry: e, ...splitEndpoint(e.endpoint) })).filter(({ method }) => readonly ? SAFE_METHODS.has(method.toUpperCase()) : true).map(({ entry, method, path }) => ({
|
|
24
|
+
name: toolName(method, path),
|
|
25
|
+
description: `${entry.summary ?? `${method} ${path}`}${entry.adminScope ? " (requires admin scope)" : ""}`,
|
|
21
26
|
inputSchema: {
|
|
22
27
|
type: "object",
|
|
23
28
|
properties: {
|
|
@@ -27,7 +32,7 @@ async function createElvixMcpServer(opts) {
|
|
|
27
32
|
},
|
|
28
33
|
required: ["path"]
|
|
29
34
|
},
|
|
30
|
-
_meta: { method
|
|
35
|
+
_meta: { method, path, adminScope: entry.adminScope ?? false }
|
|
31
36
|
}));
|
|
32
37
|
const server = new Server(
|
|
33
38
|
{ name: "elvix", version: "0.1.0" },
|
|
@@ -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/dist/mcp/bin.js
CHANGED
package/dist/mcp/index.js
CHANGED
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
|
},
|