@theone1345/smartrelay 0.2.0 → 0.3.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -17
  3. package/config/runners/agents.yaml +14 -1
  4. package/dist/credentials.d.ts +94 -0
  5. package/dist/credentials.js +168 -0
  6. package/dist/credentials.js.map +1 -0
  7. package/dist/http-api.d.ts +1 -7
  8. package/dist/http-api.js +6 -169
  9. package/dist/http-api.js.map +1 -1
  10. package/dist/index.d.ts +3 -1
  11. package/dist/index.js +5 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/install.d.ts +57 -0
  14. package/dist/install.js +216 -0
  15. package/dist/install.js.map +1 -0
  16. package/dist/logger.d.ts +2 -0
  17. package/dist/logger.js +21 -1
  18. package/dist/logger.js.map +1 -1
  19. package/dist/profiles/review-profiles.d.ts +32 -0
  20. package/dist/profiles/review-profiles.js +348 -0
  21. package/dist/profiles/review-profiles.js.map +1 -0
  22. package/dist/router.js +18 -3
  23. package/dist/router.js.map +1 -1
  24. package/dist/runners/anthropic.js +2 -3
  25. package/dist/runners/anthropic.js.map +1 -1
  26. package/dist/runners/base.d.ts +8 -0
  27. package/dist/runners/base.js +13 -0
  28. package/dist/runners/base.js.map +1 -1
  29. package/dist/runners/openai.js +2 -3
  30. package/dist/runners/openai.js.map +1 -1
  31. package/dist/runners/registry.d.ts +2 -0
  32. package/dist/runners/registry.js +4 -0
  33. package/dist/runners/registry.js.map +1 -1
  34. package/dist/server.d.ts +1 -6
  35. package/dist/server.js +134 -66
  36. package/dist/server.js.map +1 -1
  37. package/dist/setup.d.ts +65 -0
  38. package/dist/setup.js +411 -0
  39. package/dist/setup.js.map +1 -0
  40. package/dist/tools/dispatch.d.ts +33 -0
  41. package/dist/tools/dispatch.js +222 -0
  42. package/dist/tools/dispatch.js.map +1 -0
  43. package/dist/tools/handlers.d.ts +8 -2
  44. package/dist/tools/handlers.js +119 -27
  45. package/dist/tools/handlers.js.map +1 -1
  46. package/dist/util.d.ts +37 -4
  47. package/dist/util.js +71 -15
  48. package/dist/util.js.map +1 -1
  49. package/package.json +28 -3
  50. package/prompts/code_review.md +26 -20
  51. package/prompts/security_audit.md +109 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dhavan Bhalodiya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  > **The intelligent delegation layer & zero-token task server for AI agents.**
4
4
 
5
+ [![npm version](https://img.shields.io/npm/v/@theone1345/smartrelay.svg)](https://www.npmjs.com/package/@theone1345/smartrelay)
5
6
  [![MCP Standard](https://img.shields.io/badge/MCP-Standard-blue.svg)](https://modelcontextprotocol.io)
6
7
  [![TypeScript](https://img.shields.io/badge/Built%20with-TypeScript-3178C6.svg)](https://www.typescriptlang.org)
7
8
  [![Tests](https://img.shields.io/badge/Tests-39%20Passing-brightgreen.svg)](#-testing--verification)
@@ -21,11 +22,99 @@ Before installing, ensure you have:
21
22
 
22
23
  ---
23
24
 
25
+ ## 🔑 Install in One Command
26
+
27
+ ```bash
28
+ npx @theone1345/smartrelay init
29
+ ```
30
+
31
+ That single command asks for your API keys in the terminal, then registers
32
+ SmartRelay with whichever MCP clients it finds:
33
+
34
+ ```
35
+ SmartRelay setup
36
+ ────────────────
37
+ NVIDIA_API_KEY — NVIDIA NIM / API Catalog [required]
38
+ Get one at https://build.nvidia.com
39
+ key (input hidden, paste is fine):
40
+ stored nvapi-…6789
41
+
42
+ OPENROUTER_API_KEY — OpenRouter [required]
43
+ ...
44
+
45
+ Saved 2 keys to ~/.smartrelay/.env (mode 600)
46
+ 23 of 25 runners ready.
47
+
48
+ Register SmartRelay as an MCP server
49
+ ────────────────────────────────────
50
+ Add to Claude Code? [Y/n]
51
+ Add to Cursor? [Y/n]
52
+
53
+ Registering…
54
+ ✔ Claude Code — registered with the claude CLI
55
+ ✔ Cursor — added to ~/.cursor/mcp.json
56
+
57
+ Restart the client to load SmartRelay.
58
+ ```
59
+
60
+ **NVIDIA** and **OpenRouter** are required — they back all five sub-agents.
61
+ **Anthropic** and **OpenAI** are optional; press Enter to skip. Keystrokes are
62
+ hidden as you type and each key is echoed back masked.
63
+
64
+ Keys go to `~/.smartrelay/.env` at mode `600`, outside any repository. Re-running
65
+ shows what is already set and keeps it on Enter; the file is merged, never
66
+ overwritten. Client configs are merged the same way — your other MCP servers stay
67
+ put, and a backup is written before any change.
68
+
69
+ > Keys are collected in the terminal rather than through an MCP elicitation
70
+ > dialog on purpose: the elicitation schema has no masked field type, so a key
71
+ > entered that way would appear in plain text and reach the client's transcript.
72
+
73
+ ### Other ways to run it
74
+
75
+ ```bash
76
+ # Keys only, no client registration
77
+ npx @theone1345/smartrelay setup
78
+
79
+ # Print the config for every client, change nothing
80
+ npx @theone1345/smartrelay init --print-config
81
+
82
+ # Register specific clients without being asked
83
+ npx @theone1345/smartrelay init --client claude-code,cursor
84
+
85
+ # CI / Dockerfile — take keys from the environment, no prompts
86
+ NVIDIA_API_KEY=... OPENROUTER_API_KEY=... \
87
+ npx @theone1345/smartrelay init --non-interactive --client claude-code
88
+ ```
89
+
90
+ Supported client ids: `claude-code` (via the `claude` CLI), `claude-desktop`,
91
+ `cursor`, `windsurf`.
92
+
93
+ ### Where keys are read from
94
+
95
+ Highest priority wins:
96
+
97
+ | # | Source | Notes |
98
+ |---|---|---|
99
+ | 1 | `process.env` | including your MCP client's `"env": {}` block |
100
+ | 2 | `<cwd>/.env` | project-local |
101
+ | 3 | `<project root>/.env` | |
102
+ | 4 | `~/.smartrelay/.env` | written by `smartrelay setup` |
103
+
104
+ Every source is read, in that order — a key defined only in a lower-priority file
105
+ is still picked up. If no provider key resolves, the server logs a warning naming
106
+ the fix on startup (to stderr, so the stdio JSON-RPC stream stays clean) rather
107
+ than failing silently at the first tool call.
108
+
109
+ ---
110
+
24
111
  ## 🚀 Quick Install
25
112
 
26
113
  ### Option A — `npx` *(Easiest — zero install, runs everywhere)*
27
114
 
28
- Add directly to your Claude Desktop / Cursor / Windsurf configuration:
115
+ `npx @theone1345/smartrelay init` does all of this for you, including the keys.
116
+ To wire it up by hand instead, add the following to your Claude Desktop / Cursor /
117
+ Windsurf configuration:
29
118
 
30
119
  ```json
31
120
  {
@@ -118,7 +207,7 @@ Switch models on the fly during your session — just type plain English:
118
207
 
119
208
  ---
120
209
 
121
- ## 🛠️ 13 MCP Tools
210
+ ## 🛠️ 15 MCP Tools
122
211
 
123
212
  | Tool | What It Does |
124
213
  | :--- | :--- |
@@ -243,6 +332,15 @@ npm install
243
332
 
244
333
  ### 2. Configure Environment Variables
245
334
 
335
+ The quickest route is the wizard, which writes `~/.smartrelay/.env` for every
336
+ project at once:
337
+
338
+ ```bash
339
+ npm run setup
340
+ ```
341
+
342
+ Or keep the keys inside the checkout:
343
+
246
344
  ```bash
247
345
  cp .env.example .env
248
346
  ```
@@ -279,13 +377,12 @@ npm run build
279
377
 
280
378
  #### 🟣 Claude Code (CLI)
281
379
 
282
- To make SmartRelay available across **all your projects** (e.g. Flutter apps in `Documents`):
283
-
380
+ Add globally across **all projects** with a single command:
284
381
  ```bash
285
- claude mcp add -s user smartrelay -- node /ABSOLUTE/PATH/TO/smart-relay/dist/server.js
382
+ claude mcp add -s user smartrelay -- npx -y @theone1345/smartrelay
286
383
  ```
287
384
 
288
- > **Note**: The `-s user` flag installs it globally for your user account so it works inside any folder or project. Omit `-s user` only if you want it scoped to the current directory only.
385
+ *(Or if you are running locally from source: `claude mcp add -s user smartrelay -- node /ABSOLUTE/PATH/TO/smart-relay/dist/server.js`)*
289
386
 
290
387
  Verify anywhere: `claude mcp list` | Remove: `claude mcp remove -s user smartrelay`
291
388
 
@@ -300,26 +397,27 @@ Config file locations:
300
397
  {
301
398
  "mcpServers": {
302
399
  "smartrelay": {
303
- "command": "node",
304
- "args": ["/ABSOLUTE/PATH/TO/smart-relay/dist/server.js"],
400
+ "command": "npx",
401
+ "args": ["-y", "@theone1345/smartrelay"],
305
402
  "env": {
306
- "OPENAI_API_KEY": "sk-proj-...",
307
- "ANTHROPIC_API_KEY": "sk-ant-...",
403
+ "OPENROUTER_API_KEY": "sk-or-v1-...",
308
404
  "NVIDIA_API_KEY": "nvapi-...",
309
- "OPENROUTER_API_KEY": "sk-or-v1-..."
405
+ "ANTHROPIC_API_KEY": "sk-ant-...",
406
+ "OPENAI_API_KEY": "sk-proj-..."
310
407
  }
311
408
  }
312
409
  }
313
410
  }
314
411
  ```
315
- *(Note: If you already configured `.env` inside the `smart-relay` repository, the `"env"` block above is optional — SmartRelay auto-detects it!)*
316
412
 
317
413
  #### 🔵 Cursor IDE
318
414
 
319
415
  Go to **Settings → Features → MCP → + Add New MCP Server**:
320
416
  - **Name**: `smartrelay`
321
417
  - **Type**: `command`
322
- - **Command**: `node /ABSOLUTE/PATH/TO/smart-relay/dist/server.js`
418
+ - **Command**: `npx -y @theone1345/smartrelay`
419
+
420
+ *(Or if running locally: `/opt/homebrew/bin/node /ABSOLUTE/PATH/TO/smart-relay/dist/server.js`)*
323
421
 
324
422
  #### 🌊 Windsurf / Codeium
325
423
 
@@ -328,8 +426,12 @@ Edit `~/.codeium/windsurf/mcp_config.json`:
328
426
  {
329
427
  "mcpServers": {
330
428
  "smartrelay": {
331
- "command": "node",
332
- "args": ["/ABSOLUTE/PATH/TO/smart-relay/dist/server.js"]
429
+ "command": "npx",
430
+ "args": ["-y", "@theone1345/smartrelay"],
431
+ "env": {
432
+ "OPENROUTER_API_KEY": "sk-or-v1-...",
433
+ "NVIDIA_API_KEY": "nvapi-..."
434
+ }
333
435
  }
334
436
  }
335
437
  }
@@ -342,8 +444,12 @@ Edit your Cline MCP settings (`cline_mcp_settings.json`):
342
444
  {
343
445
  "mcpServers": {
344
446
  "smartrelay": {
345
- "command": "node",
346
- "args": ["/ABSOLUTE/PATH/TO/smart-relay/dist/server.js"]
447
+ "command": "npx",
448
+ "args": ["-y", "@theone1345/smartrelay"],
449
+ "env": {
450
+ "OPENROUTER_API_KEY": "sk-or-v1-...",
451
+ "NVIDIA_API_KEY": "nvapi-..."
452
+ }
347
453
  }
348
454
  }
349
455
  }
@@ -435,6 +541,10 @@ Opens at **http://localhost:6274**
435
541
  ## 🔒 Security Notes
436
542
 
437
543
  - API keys are read from environment variables only — never hardcoded
544
+ - `smartrelay setup` stores keys in `~/.smartrelay/.env` at mode `600`, written
545
+ via a temp file and rename so an interrupted run leaves nothing half-written
546
+ - Keys are never echoed in full — the wizard and all logs show a masked form, and
547
+ `smartrelay_get_logs` redacts anything matching a provider key prefix
438
548
  - The HTTP API requires a Bearer token (`SMARTRELAY_HTTP_API_KEY`)
439
549
  - The MCP stdio server is local-only (no network exposure)
440
550
  - No `process.exit()` calls — uses `process.exitCode` for safe shutdown
@@ -19,8 +19,9 @@ runners:
19
19
  api_key_env: "NVIDIA_API_KEY"
20
20
  cost_per_million_input_tokens: 0.70
21
21
  cost_per_million_output_tokens: 0.90
22
+ timeout_seconds: 120.0
22
23
  default_params:
23
- max_tokens: 4096
24
+ max_tokens: 16384
24
25
  temperature: 0.1
25
26
  system_prompt_file: "prompts/code_review.md"
26
27
 
@@ -45,3 +46,15 @@ runners:
45
46
  max_tokens: 4096
46
47
  temperature: 0.3
47
48
  system_prompt_file: "prompts/explain_code.md"
49
+
50
+ security-agent:
51
+ type: "nvidia"
52
+ model: "nvidia/nemotron-3-super-120b-a12b"
53
+ api_key_env: "NVIDIA_API_KEY"
54
+ cost_per_million_input_tokens: 0.70
55
+ cost_per_million_output_tokens: 0.90
56
+ timeout_seconds: 120.0
57
+ default_params:
58
+ max_tokens: 16384
59
+ temperature: 0.1
60
+ system_prompt_file: "prompts/security_audit.md"
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Shared vocabulary for "which provider keys matter".
3
+ *
4
+ * Used by the setup wizard (`src/setup.ts`), the server's startup credential
5
+ * warning, the runner auth-error message, and the MCPHub plugin — so there is
6
+ * exactly one list of providers and one definition of what "ready" means.
7
+ */
8
+ import type { Logger } from './logger.js';
9
+ import type { RunnerMetadata } from './runners/registry.js';
10
+ /** Published package name, used to build every install and setup command. */
11
+ export declare const PACKAGE_NAME = "@theone1345/smartrelay";
12
+ /** The exact command the user should run to configure credentials. */
13
+ export declare const SETUP_COMMAND = "npx @theone1345/smartrelay setup";
14
+ /** One-shot command: prompts for keys, then registers the MCP server. */
15
+ export declare const INSTALL_COMMAND = "npx @theone1345/smartrelay init";
16
+ /** One-line nudge appended to errors and warnings. */
17
+ export declare const SETUP_HINT = "Run `npx @theone1345/smartrelay setup` to store your provider API keys.";
18
+ export interface KeySpec {
19
+ /** Environment variable the runners actually read. */
20
+ env: string;
21
+ /** Human-readable provider name shown in the wizard. */
22
+ label: string;
23
+ /** Required keys block a successful setup; optional ones can be skipped. */
24
+ required: boolean;
25
+ /** Expected key prefix, used for a warning only — never to reject a value. */
26
+ prefix: string | null;
27
+ /** Where the user goes to obtain the key. */
28
+ consoleUrl: string;
29
+ /** Matching field name in the MCPHub plugin's config schema. */
30
+ configField: string;
31
+ }
32
+ /**
33
+ * Providers the wizard asks about, in prompt order.
34
+ *
35
+ * NVIDIA and OpenRouter are required because every sub-agent in
36
+ * `config/runners/agents.yaml` is backed by one of them. Anthropic and OpenAI
37
+ * unlock additional runners but nothing depends on them.
38
+ */
39
+ export declare const PROVIDER_KEYS: readonly KeySpec[];
40
+ /** Look up a spec by environment variable name. */
41
+ export declare function findKeySpec(envVar: string): KeySpec | undefined;
42
+ /**
43
+ * Render a secret for display: enough to recognize, never enough to use.
44
+ *
45
+ * Short values are replaced wholesale — a 10-character key would otherwise be
46
+ * almost fully revealed by a first-6/last-4 split.
47
+ */
48
+ export declare function maskKey(value: string): string;
49
+ /**
50
+ * Check a key against its expected shape.
51
+ *
52
+ * Returns a warning string or `null`. It never throws and never rejects:
53
+ * providers rotate their key formats, and a wizard that refuses a valid new-style
54
+ * key is worse than one that saves an invalid one.
55
+ */
56
+ export declare function validateKeyFormat(spec: KeySpec, value: string): string | null;
57
+ export interface CredentialSummary {
58
+ /** Runners whose credential env var is populated. */
59
+ ready: number;
60
+ /** Total registered runners. */
61
+ total: number;
62
+ /** Deduped env vars that unauthenticated runners are waiting on, in config order. */
63
+ missingEnvVars: string[];
64
+ /** Required provider keys (per `PROVIDER_KEYS`) absent from the environment. */
65
+ missingRequired: string[];
66
+ }
67
+ /**
68
+ * True when not one required provider key is set.
69
+ *
70
+ * Deliberately not `ready === 0`: runners that need no credential (Ollama)
71
+ * report as authenticated, so a count alone reads "healthy" on an install where
72
+ * every hosted model is unreachable.
73
+ */
74
+ export declare function noProviderKeysConfigured(summary: CredentialSummary): boolean;
75
+ /** Roll runner metadata up into the counts the wizard and startup warning print. */
76
+ export declare function summarizeCredentials(metadata: RunnerMetadata[]): CredentialSummary;
77
+ /**
78
+ * Render the runner readiness table.
79
+ *
80
+ * Shared by `smartrelay setup` and `npm run quick-test -- --list` so both always
81
+ * show the same thing.
82
+ */
83
+ export declare function formatRunnerStatusLines(metadata: RunnerMetadata[]): string[];
84
+ /**
85
+ * Tell the operator, at startup, that nothing will work.
86
+ *
87
+ * Two hard rules, both load-bearing:
88
+ * - Log only. `getLogger` writes to stderr; a single byte on stdout would
89
+ * corrupt the stdio transport's JSON-RPC framing.
90
+ * - Never throw and never exit. An MCP client renders a non-zero exit as
91
+ * "server crashed", and missing credentials must stay a recoverable state
92
+ * the user can fix without restarting anything.
93
+ */
94
+ export declare function warnIfNoCredentials(metadata: RunnerMetadata[], logger: Logger): void;
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Shared vocabulary for "which provider keys matter".
3
+ *
4
+ * Used by the setup wizard (`src/setup.ts`), the server's startup credential
5
+ * warning, the runner auth-error message, and the MCPHub plugin — so there is
6
+ * exactly one list of providers and one definition of what "ready" means.
7
+ */
8
+ /** Published package name, used to build every install and setup command. */
9
+ export const PACKAGE_NAME = '@theone1345/smartrelay';
10
+ /** The exact command the user should run to configure credentials. */
11
+ export const SETUP_COMMAND = `npx ${PACKAGE_NAME} setup`;
12
+ /** One-shot command: prompts for keys, then registers the MCP server. */
13
+ export const INSTALL_COMMAND = `npx ${PACKAGE_NAME} init`;
14
+ /** One-line nudge appended to errors and warnings. */
15
+ export const SETUP_HINT = `Run \`${SETUP_COMMAND}\` to store your provider API keys.`;
16
+ /**
17
+ * Providers the wizard asks about, in prompt order.
18
+ *
19
+ * NVIDIA and OpenRouter are required because every sub-agent in
20
+ * `config/runners/agents.yaml` is backed by one of them. Anthropic and OpenAI
21
+ * unlock additional runners but nothing depends on them.
22
+ */
23
+ export const PROVIDER_KEYS = [
24
+ {
25
+ env: 'NVIDIA_API_KEY',
26
+ label: 'NVIDIA NIM / API Catalog',
27
+ required: true,
28
+ prefix: 'nvapi-',
29
+ consoleUrl: 'https://build.nvidia.com',
30
+ configField: 'nvidiaApiKey',
31
+ },
32
+ {
33
+ env: 'OPENROUTER_API_KEY',
34
+ label: 'OpenRouter',
35
+ required: true,
36
+ prefix: 'sk-or-v1-',
37
+ consoleUrl: 'https://openrouter.ai/keys',
38
+ configField: 'openrouterApiKey',
39
+ },
40
+ {
41
+ env: 'ANTHROPIC_API_KEY',
42
+ label: 'Anthropic',
43
+ required: false,
44
+ prefix: 'sk-ant-',
45
+ consoleUrl: 'https://console.anthropic.com/settings/keys',
46
+ configField: 'anthropicApiKey',
47
+ },
48
+ {
49
+ env: 'OPENAI_API_KEY',
50
+ label: 'OpenAI',
51
+ required: false,
52
+ prefix: 'sk-',
53
+ consoleUrl: 'https://platform.openai.com/api-keys',
54
+ configField: 'openaiApiKey',
55
+ },
56
+ ];
57
+ /** Look up a spec by environment variable name. */
58
+ export function findKeySpec(envVar) {
59
+ return PROVIDER_KEYS.find((spec) => spec.env === envVar);
60
+ }
61
+ /** Shortest key length that still leaves something to hide in the middle. */
62
+ const MASKABLE_LENGTH = 12;
63
+ /**
64
+ * Render a secret for display: enough to recognize, never enough to use.
65
+ *
66
+ * Short values are replaced wholesale — a 10-character key would otherwise be
67
+ * almost fully revealed by a first-6/last-4 split.
68
+ */
69
+ export function maskKey(value) {
70
+ const trimmed = value.trim();
71
+ if (trimmed.length < MASKABLE_LENGTH)
72
+ return '****';
73
+ return `${trimmed.slice(0, 6)}…${trimmed.slice(-4)}`;
74
+ }
75
+ /**
76
+ * Check a key against its expected shape.
77
+ *
78
+ * Returns a warning string or `null`. It never throws and never rejects:
79
+ * providers rotate their key formats, and a wizard that refuses a valid new-style
80
+ * key is worse than one that saves an invalid one.
81
+ */
82
+ export function validateKeyFormat(spec, value) {
83
+ const trimmed = value.trim();
84
+ if (!trimmed)
85
+ return null;
86
+ if (spec.prefix && !trimmed.startsWith(spec.prefix)) {
87
+ return `expected ${spec.env} to start with "${spec.prefix}"`;
88
+ }
89
+ if (trimmed.length < 20) {
90
+ return `${spec.env} looks short (${trimmed.length} characters)`;
91
+ }
92
+ if (/\s/.test(trimmed)) {
93
+ return `${spec.env} contains whitespace`;
94
+ }
95
+ return null;
96
+ }
97
+ /**
98
+ * True when not one required provider key is set.
99
+ *
100
+ * Deliberately not `ready === 0`: runners that need no credential (Ollama)
101
+ * report as authenticated, so a count alone reads "healthy" on an install where
102
+ * every hosted model is unreachable.
103
+ */
104
+ export function noProviderKeysConfigured(summary) {
105
+ return summary.missingRequired.length === PROVIDER_KEYS.filter((spec) => spec.required).length;
106
+ }
107
+ /** Roll runner metadata up into the counts the wizard and startup warning print. */
108
+ export function summarizeCredentials(metadata) {
109
+ const missingEnvVars = [];
110
+ let ready = 0;
111
+ for (const runner of metadata) {
112
+ if (runner.is_authenticated) {
113
+ ready++;
114
+ continue;
115
+ }
116
+ const envVar = runner.credentials_env_var;
117
+ if (envVar && !missingEnvVars.includes(envVar))
118
+ missingEnvVars.push(envVar);
119
+ }
120
+ const missingRequired = PROVIDER_KEYS.filter((spec) => spec.required && !process.env[spec.env]).map((spec) => spec.env);
121
+ return { ready, total: metadata.length, missingEnvVars, missingRequired };
122
+ }
123
+ /**
124
+ * Render the runner readiness table.
125
+ *
126
+ * Shared by `smartrelay setup` and `npm run quick-test -- --list` so both always
127
+ * show the same thing.
128
+ */
129
+ export function formatRunnerStatusLines(metadata) {
130
+ return metadata.map((runner) => {
131
+ const auth = runner.is_authenticated ? '🟢 AUTHENTICATED' : '🔴 MISSING_AUTH';
132
+ const cost = `$${runner.pricing.cost_per_million_input_tokens}/` +
133
+ `$${runner.pricing.cost_per_million_output_tokens} per M`;
134
+ return `- ${runner.runner_id.padEnd(30)} [${runner.model}] ${auth.padEnd(20)} ${cost}`;
135
+ });
136
+ }
137
+ /**
138
+ * Tell the operator, at startup, that nothing will work.
139
+ *
140
+ * Two hard rules, both load-bearing:
141
+ * - Log only. `getLogger` writes to stderr; a single byte on stdout would
142
+ * corrupt the stdio transport's JSON-RPC framing.
143
+ * - Never throw and never exit. An MCP client renders a non-zero exit as
144
+ * "server crashed", and missing credentials must stay a recoverable state
145
+ * the user can fix without restarting anything.
146
+ */
147
+ export function warnIfNoCredentials(metadata, logger) {
148
+ let summary;
149
+ try {
150
+ summary = summarizeCredentials(metadata);
151
+ }
152
+ catch {
153
+ return;
154
+ }
155
+ if (summary.total === 0)
156
+ return;
157
+ if (noProviderKeysConfigured(summary)) {
158
+ logger.warning(`No provider API keys detected — only ${summary.ready} of ${summary.total} runners are usable ` +
159
+ '(those needing no credentials). Every hosted model will fail until a key is configured.');
160
+ logger.warning(`Fix: ${SETUP_COMMAND}`);
161
+ logger.warning(`Or set ${summary.missingRequired.join(' / ')} in your MCP client's "env" block.`);
162
+ }
163
+ else if (summary.missingEnvVars.length) {
164
+ logger.info(`Credentials: ${summary.ready}/${summary.total} runners authenticated. ` +
165
+ `Missing: ${summary.missingEnvVars.join(', ')}.`);
166
+ }
167
+ }
168
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
@@ -7,14 +7,8 @@
7
7
  * Transport: HTTP/JSON with Bearer token auth.
8
8
  */
9
9
  import { type FastifyInstance, type FastifyReply, type FastifyRequest } from 'fastify';
10
- import { BenchmarkEngine } from './benchmark/engine.js';
11
- import { TaskRouter } from './router.js';
12
- import { RunnerRegistry } from './runners/registry.js';
13
- export declare function getRegistry(configPath?: string | null): RunnerRegistry;
14
- export declare function getEngine(): BenchmarkEngine;
15
- export declare function getRouter(): TaskRouter;
10
+ export { createDispatchContext, dispatchTool, getEngine, getRegistry, getRouter } from './tools/dispatch.js';
16
11
  export declare function verifyApiKey(request: FastifyRequest, reply: FastifyReply): void;
17
- export declare function dispatchTool(toolName: string, args?: Record<string, unknown>): Promise<unknown>;
18
12
  export declare function createHttpServer(): FastifyInstance;
19
13
  export declare function runHttpServer(options?: {
20
14
  config?: string;