@juspay/neurolink 13.8.17 → 13.8.19
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/CHANGELOG.md +5 -2
- package/README.md +1 -1
- package/dist/action/actionExecutor.d.ts +33 -3
- package/dist/action/actionExecutor.js +71 -15
- package/dist/action/actionInputs.js +4 -1
- package/dist/action/cliVersion.d.ts +25 -0
- package/dist/action/cliVersion.js +26 -0
- package/dist/action/index.d.ts +2 -1
- package/dist/action/index.js +2 -1
- package/dist/adapters/video/directorPipeline.js +46 -36
- package/dist/adapters/video/ffmpegAdapter.d.ts +17 -0
- package/dist/adapters/video/ffmpegAdapter.js +32 -5
- package/dist/adapters/video/frameExtractor.js +1 -1
- package/dist/adapters/video/videoMerger.js +5 -2
- package/dist/agent/arithmeticEvaluator.d.ts +32 -0
- package/dist/agent/arithmeticEvaluator.js +349 -0
- package/dist/agent/directTools.d.ts +6 -18
- package/dist/agent/directTools.js +17 -56
- package/dist/artifacts/artifactStore.d.ts +22 -1
- package/dist/artifacts/artifactStore.js +66 -6
- package/dist/auth/anthropicOAuth.d.ts +26 -1
- package/dist/auth/anthropicOAuth.js +81 -5
- package/dist/auth/secureFiles.d.ts +37 -0
- package/dist/auth/secureFiles.js +95 -0
- package/dist/auth/tokenStore.d.ts +43 -6
- package/dist/auth/tokenStore.js +260 -79
- package/dist/avatar/providers/DIDAvatar.js +49 -45
- package/dist/avatar/providers/ReplicateAvatar.js +33 -40
- package/dist/browser/neurolink.min.js +485 -558
- package/dist/cli/commands/auth.js +112 -70
- package/dist/cli/commands/proxy.d.ts +4 -0
- package/dist/cli/commands/proxy.js +128 -13
- package/dist/cli/commands/serve.d.ts +10 -1
- package/dist/cli/commands/serve.js +98 -17
- package/dist/cli/commands/server.js +17 -5
- package/dist/cli/index.js +146 -5
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/utils/envManager.js +17 -3
- package/dist/cli/utils/serverUtils.d.ts +39 -3
- package/dist/cli/utils/serverUtils.js +63 -6
- package/dist/core/baseProvider.js +7 -1
- package/dist/core/modules/MessageBuilder.js +5 -0
- package/dist/core/modules/ToolsManager.d.ts +46 -1
- package/dist/core/modules/ToolsManager.js +190 -43
- package/dist/files/fileReferenceRegistry.d.ts +2 -23
- package/dist/files/fileReferenceRegistry.js +55 -5
- package/dist/hitl/hitlManager.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -1
- package/dist/mcp/auth/tokenStorage.d.ts +7 -0
- package/dist/mcp/auth/tokenStorage.js +23 -4
- package/dist/mcp/externalServerManager.d.ts +13 -3
- package/dist/mcp/externalServerManager.js +154 -136
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/mcpClientFactory.d.ts +14 -0
- package/dist/mcp/mcpClientFactory.js +70 -7
- package/dist/mcp/projectConfigTrust.d.ts +72 -0
- package/dist/mcp/projectConfigTrust.js +237 -0
- package/dist/mcp/transportUrlPolicy.d.ts +39 -0
- package/dist/mcp/transportUrlPolicy.js +172 -0
- package/dist/music/providers/BeatovenMusic.js +35 -46
- package/dist/music/providers/ReplicateMusic.js +33 -40
- package/dist/neurolink.d.ts +16 -1
- package/dist/neurolink.js +64 -11
- package/dist/processors/archive/ArchiveProcessor.js +15 -1
- package/dist/processors/archive/zipEntryIndex.d.ts +22 -0
- package/dist/processors/archive/zipEntryIndex.js +33 -0
- package/dist/processors/base/BaseFileProcessor.d.ts +9 -21
- package/dist/processors/base/BaseFileProcessor.js +40 -111
- package/dist/processors/data/YamlProcessor.d.ts +13 -1
- package/dist/processors/data/YamlProcessor.js +93 -2
- package/dist/processors/document/OpenDocumentProcessor.js +7 -2
- package/dist/processors/document/PptxProcessor.js +12 -7
- package/dist/processors/media/VideoProcessor.js +93 -15
- package/dist/providers/openaiChatCompletionsClient.js +12 -3
- package/dist/proxy/accountLedger.js +6 -8
- package/dist/proxy/geminiFormat.d.ts +3 -11
- package/dist/proxy/geminiFormat.js +11 -2
- package/dist/proxy/inboundAuth.d.ts +89 -0
- package/dist/proxy/inboundAuth.js +219 -0
- package/dist/proxy/openaiFormat.d.ts +3 -9
- package/dist/proxy/openaiFormat.js +21 -15
- package/dist/proxy/proxyTranslationEngine.d.ts +2 -6
- package/dist/proxy/proxyTranslationEngine.js +25 -14
- package/dist/proxy/routingPolicy.d.ts +3 -1
- package/dist/proxy/routingPolicy.js +7 -8
- package/dist/proxy/tokenRefresh.d.ts +6 -0
- package/dist/proxy/tokenRefresh.js +13 -1
- package/dist/proxy/updateState.js +4 -4
- package/dist/rag/document/loaders.d.ts +0 -6
- package/dist/rag/document/loaders.js +16 -10
- package/dist/rag/ragIntegration.d.ts +2 -2
- package/dist/rag/ragIntegration.js +35 -10
- package/dist/rag/retrieval/vectorQueryTool.d.ts +7 -0
- package/dist/rag/retrieval/vectorQueryTool.js +54 -17
- package/dist/server/abstract/baseServerAdapter.d.ts +43 -0
- package/dist/server/abstract/baseServerAdapter.js +119 -6
- package/dist/server/adapters/expressAdapter.d.ts +3 -0
- package/dist/server/adapters/expressAdapter.js +78 -6
- package/dist/server/adapters/fastifyAdapter.d.ts +3 -0
- package/dist/server/adapters/fastifyAdapter.js +165 -70
- package/dist/server/adapters/honoAdapter.d.ts +9 -1
- package/dist/server/adapters/honoAdapter.js +99 -21
- package/dist/server/adapters/koaAdapter.d.ts +1 -0
- package/dist/server/adapters/koaAdapter.js +153 -74
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +4 -0
- package/dist/server/middleware/auth.js +6 -2
- package/dist/server/middleware/cache.js +6 -1
- package/dist/server/middleware/rateLimit.js +29 -4
- package/dist/server/routes/agentRoutes.js +24 -11
- package/dist/server/routes/claudeProxyRoutes.d.ts +1 -0
- package/dist/server/routes/claudeProxyRoutes.js +273 -84
- package/dist/server/routes/codexProxyRoutes.d.ts +2 -2
- package/dist/server/routes/codexProxyRoutes.js +125 -40
- package/dist/server/routes/index.d.ts +4 -0
- package/dist/server/routes/index.js +35 -4
- package/dist/server/routes/mcpRoutes.d.ts +6 -2
- package/dist/server/routes/mcpRoutes.js +21 -7
- package/dist/server/routes/memoryRoutes.js +7 -6
- package/dist/server/routes/toolRoutes.d.ts +12 -2
- package/dist/server/routes/toolRoutes.js +33 -3
- package/dist/server/routes/transcribeRoutes.js +60 -13
- package/dist/server/utils/clientAddress.d.ts +23 -0
- package/dist/server/utils/clientAddress.js +65 -0
- package/dist/server/utils/clientError.d.ts +35 -0
- package/dist/server/utils/clientError.js +129 -0
- package/dist/server/utils/pathMatch.d.ts +21 -0
- package/dist/server/utils/pathMatch.js +41 -0
- package/dist/server/utils/validation.d.ts +9 -0
- package/dist/server/utils/validation.js +9 -0
- package/dist/server/websocket/WebSocketHandler.js +22 -3
- package/dist/server/websocket/frameQueue.d.ts +19 -0
- package/dist/server/websocket/frameQueue.js +41 -3
- package/dist/server/websocket/transcribeWebSocket.d.ts +8 -1
- package/dist/server/websocket/transcribeWebSocket.js +223 -34
- package/dist/skills/skillStores.js +5 -0
- package/dist/tasks/store/fileTaskStore.d.ts +2 -0
- package/dist/tasks/store/fileTaskStore.js +11 -4
- package/dist/types/attachments.d.ts +117 -0
- package/dist/types/attachments.js +15 -0
- package/dist/types/auth.d.ts +6 -0
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/config.d.ts +56 -0
- package/dist/types/file.d.ts +8 -0
- package/dist/types/hitl.d.ts +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/mcp.d.ts +98 -0
- package/dist/types/multimodal.js +0 -41
- package/dist/types/openaiCompatible.d.ts +1 -1
- package/dist/types/processor.d.ts +14 -1
- package/dist/types/proxy.d.ts +71 -16
- package/dist/types/proxyClient.d.ts +2 -0
- package/dist/types/rag.d.ts +9 -0
- package/dist/types/safeFetch.d.ts +40 -0
- package/dist/types/server.d.ts +206 -10
- package/dist/types/tools.d.ts +57 -0
- package/dist/types/transcribe.d.ts +6 -0
- package/dist/utils/attachmentPolicy.d.ts +89 -0
- package/dist/utils/attachmentPolicy.js +327 -0
- package/dist/utils/dotenvBootstrap.d.ts +48 -19
- package/dist/utils/dotenvBootstrap.js +172 -31
- package/dist/utils/fileDetector.js +115 -65
- package/dist/utils/json/coerce.js +11 -1
- package/dist/utils/logSanitize.d.ts +5 -0
- package/dist/utils/logSanitize.js +8 -0
- package/dist/utils/logger.js +73 -1
- package/dist/utils/messageBuilder.d.ts +2 -2
- package/dist/utils/messageBuilder.js +118 -105
- package/dist/utils/multimodalOptionsBuilder.d.ts +1 -0
- package/dist/utils/multimodalOptionsBuilder.js +2 -0
- package/dist/utils/redis.d.ts +12 -0
- package/dist/utils/redis.js +72 -0
- package/dist/utils/retryAfter.d.ts +32 -0
- package/dist/utils/retryAfter.js +135 -11
- package/dist/utils/safeFetch.d.ts +22 -1
- package/dist/utils/safeFetch.js +132 -38
- package/dist/utils/safeStoreId.d.ts +19 -0
- package/dist/utils/safeStoreId.js +29 -0
- package/dist/utils/schemaConversion.d.ts +30 -3
- package/dist/utils/schemaConversion.js +457 -84
- package/dist/utils/sizeGuard.js +2 -2
- package/dist/utils/ssrfGuard.d.ts +3 -3
- package/dist/utils/ssrfGuard.js +71 -39
- package/dist/voice/eval/sttEvalStore.d.ts +26 -3
- package/dist/voice/eval/sttEvalStore.js +77 -12
- package/dist/voice/livekit/agentDefinition.d.ts +18 -0
- package/dist/voice/livekit/agentDefinition.js +22 -0
- package/dist/voice/livekit/realtimeMcpTools.d.ts +1 -1
- package/dist/voice/livekit/realtimeMcpTools.js +8 -7
- package/dist/voice/livekit/realtimeVoiceAgent.d.ts +2 -4
- package/dist/voice/livekit/realtimeVoiceAgent.js +18 -7
- package/dist/voice/livekit/voiceAgent.d.ts +2 -2
- package/dist/voice/livekit/voiceAgent.js +2 -1
- package/dist/voice/livekit/xaiSessionWatch.d.ts +0 -5
- package/dist/voice/livekit/xaiSessionWatch.js +30 -0
- package/dist/voice/providers/GeminiLive.js +21 -6
- package/dist/voice/providers/GoogleSTT.js +8 -5
- package/dist/voice/transcribe.d.ts +2 -2
- package/dist/voice/transcribe.js +34 -18
- package/dist/voice/whistle/audio.js +11 -5
- package/dist/workflow/workflows/adaptiveWorkflow.d.ts +2 -2
- package/dist/workflow/workflows/adaptiveWorkflow.js +10 -10
- package/dist/workflow/workflows/consensusWorkflow.d.ts +4 -4
- package/dist/workflow/workflows/consensusWorkflow.js +12 -12
- package/dist/workflow/workflows/fallbackWorkflow.d.ts +3 -3
- package/dist/workflow/workflows/fallbackWorkflow.js +7 -7
- package/dist/workflow/workflows/multiJudgeWorkflow.d.ts +7 -7
- package/dist/workflow/workflows/multiJudgeWorkflow.js +31 -31
- package/docs-site/static/search-index.json +74 -52
- package/package.json +173 -156
- package/scripts/observability/import-openobserve-dashboard.mjs +11 -3
|
@@ -16,7 +16,7 @@ import { NeuroLink } from "../../neurolink.js";
|
|
|
16
16
|
import { ConfigurationError, ServerStartError, } from "../../server/errors.js";
|
|
17
17
|
import { withTimeout } from "../../utils/errorHandling.js";
|
|
18
18
|
import { logger } from "../../utils/logger.js";
|
|
19
|
-
import { clearStateOnExit, createServerApiKeyMiddleware, formatUptime, isOtherServerRunning, isProcessRunning, readServerApiKeys, resolveServerPort, SERVER_API_KEY_ENV, StateFileManager, } from "../utils/serverUtils.js";
|
|
19
|
+
import { clearStateOnExit, createServerApiKeyMiddleware, DEFAULT_SERVER_HOST, envFlag, formatUptime, isLoopbackHost, isOtherServerRunning, isProcessRunning, readServerApiKeys, resolveServerPort, SERVER_ALLOW_UNAUTHENTICATED_ENV, SERVER_API_KEY_ENV, SERVER_ENABLE_BUILTIN_TOOLS_ENV, SERVER_WS_ALLOW_QUERY_TOKEN_ENV, StateFileManager, unauthenticatedBindRefusal, } from "../utils/serverUtils.js";
|
|
20
20
|
// ============================================
|
|
21
21
|
// State Management
|
|
22
22
|
// ============================================
|
|
@@ -159,7 +159,7 @@ function corsOriginsFromArgv(values) {
|
|
|
159
159
|
.filter((origin) => origin.length > 0);
|
|
160
160
|
return origins.length > 0 ? origins : undefined;
|
|
161
161
|
}
|
|
162
|
-
async function attachTranscriptionWebSocket(server, neurolink, serverConfig) {
|
|
162
|
+
async function attachTranscriptionWebSocket(server, neurolink, serverConfig, hardening) {
|
|
163
163
|
const native = server.getNativeServer?.();
|
|
164
164
|
if (!isNodeHttpServer(native)) {
|
|
165
165
|
return;
|
|
@@ -175,9 +175,28 @@ async function attachTranscriptionWebSocket(server, neurolink, serverConfig) {
|
|
|
175
175
|
await attachTranscribeWebSocket(native, neurolink, {
|
|
176
176
|
...(apiKeys.length > 0 ? { authToken: apiKeys } : {}),
|
|
177
177
|
...(allowedOrigins ? { allowedOrigins } : {}),
|
|
178
|
+
// The key rides in the Authorization header or the subprotocol; a URL is
|
|
179
|
+
// logged, so `?token=` is an explicit choice.
|
|
180
|
+
allowQueryToken: envFlag(SERVER_WS_ALLOW_QUERY_TOKEN_ENV),
|
|
181
|
+
// The same opt-out as the HTTP routes: whoever runs open on purpose also
|
|
182
|
+
// gets file mode without a key.
|
|
183
|
+
allowUnauthenticatedFileMode: hardening.allowUnauthenticated === true,
|
|
178
184
|
});
|
|
179
185
|
logger.debug("[serve] transcription WebSocket attached at /v1/audio/transcriptions/stream");
|
|
180
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* The hardening flags, from the command line or the environment:
|
|
189
|
+
* `--allow-unauthenticated` / NEUROLINK_SERVER_ALLOW_UNAUTHENTICATED=1 and
|
|
190
|
+
* `--enable-builtin-tools` / NEUROLINK_SERVER_ENABLE_BUILTIN_TOOLS=1.
|
|
191
|
+
*/
|
|
192
|
+
function resolveHardening(argv) {
|
|
193
|
+
return {
|
|
194
|
+
allowUnauthenticated: argv.allowUnauthenticated === true ||
|
|
195
|
+
envFlag(SERVER_ALLOW_UNAUTHENTICATED_ENV),
|
|
196
|
+
enableBuiltinTools: argv.enableBuiltinTools === true ||
|
|
197
|
+
envFlag(SERVER_ENABLE_BUILTIN_TOOLS_ENV),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
181
200
|
export class ServeCommandFactory {
|
|
182
201
|
/**
|
|
183
202
|
* Create the main serve command
|
|
@@ -208,7 +227,19 @@ export class ServeCommandFactory {
|
|
|
208
227
|
.option("host", {
|
|
209
228
|
type: "string",
|
|
210
229
|
alias: "H",
|
|
211
|
-
description:
|
|
230
|
+
description: `Host to bind to (default: ${DEFAULT_SERVER_HOST}, this machine only; 0.0.0.0 listens on every interface and needs ${SERVER_API_KEY_ENV} or --allow-unauthenticated)`,
|
|
231
|
+
})
|
|
232
|
+
.option("allow-unauthenticated", {
|
|
233
|
+
type: "boolean",
|
|
234
|
+
alias: "allowUnauthenticated",
|
|
235
|
+
default: false,
|
|
236
|
+
description: `Listen on a non-loopback host with no ${SERVER_API_KEY_ENV}, and serve tool and MCP execution to anyone who can reach the port (${SERVER_ALLOW_UNAUTHENTICATED_ENV}=1)`,
|
|
237
|
+
})
|
|
238
|
+
.option("enable-builtin-tools", {
|
|
239
|
+
type: "boolean",
|
|
240
|
+
alias: "enableBuiltinTools",
|
|
241
|
+
default: false,
|
|
242
|
+
description: `Keep NeuroLink's built-in tools, file reads and writes under the working directory among them, in the server's registry (${SERVER_ENABLE_BUILTIN_TOOLS_ENV}=1)`,
|
|
212
243
|
})
|
|
213
244
|
.option("framework", {
|
|
214
245
|
type: "string",
|
|
@@ -275,7 +306,7 @@ export class ServeCommandFactory {
|
|
|
275
306
|
.example("neurolink serve --cors --rate-limit 50", "Start server with CORS and rate limiting (50 req/15min)")
|
|
276
307
|
.example("neurolink serve --swagger", "Start server with OpenAPI documentation enabled")
|
|
277
308
|
.example("neurolink serve --watch", "Start server in watch mode (restart on changes)")
|
|
278
|
-
.example("NEUROLINK_SERVER_API_KEY=secret neurolink serve", "
|
|
309
|
+
.example("NEUROLINK_SERVER_API_KEY=secret neurolink serve --host 0.0.0.0", "Listen on every interface; require 'Authorization: Bearer secret' (or X-API-Key) on every route except the health probes")
|
|
279
310
|
.example("neurolink serve status", "Show server status")
|
|
280
311
|
.help();
|
|
281
312
|
},
|
|
@@ -319,9 +350,24 @@ export class ServeCommandFactory {
|
|
|
319
350
|
const fileConfig = ServeCommandFactory.loadFileConfig(argv, spinner);
|
|
320
351
|
// Merge CLI args with file config (CLI takes precedence)
|
|
321
352
|
const port = resolveServerPort(argv.port, fileConfig.port);
|
|
322
|
-
const host = argv.host ?? fileConfig.host ??
|
|
353
|
+
const host = argv.host ?? fileConfig.host ?? DEFAULT_SERVER_HOST;
|
|
323
354
|
const framework = argv.framework ?? fileConfig.framework ?? "hono";
|
|
324
355
|
const basePath = argv.basePath ?? fileConfig.basePath ?? "/api";
|
|
356
|
+
const hardening = resolveHardening(argv);
|
|
357
|
+
// A listener the network can reach with no key is refused unless the
|
|
358
|
+
// operator asked for exactly that.
|
|
359
|
+
const refusal = unauthenticatedBindRefusal({
|
|
360
|
+
host,
|
|
361
|
+
apiKeyCount: readServerApiKeys().length,
|
|
362
|
+
allowUnauthenticated: hardening.allowUnauthenticated === true,
|
|
363
|
+
});
|
|
364
|
+
if (refusal) {
|
|
365
|
+
if (spinner) {
|
|
366
|
+
spinner.fail(chalk.red("Refusing to start"));
|
|
367
|
+
}
|
|
368
|
+
logger.error(chalk.red(refusal));
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
325
371
|
// Build server adapter config from merged values
|
|
326
372
|
const serverConfig = ServeCommandFactory.buildServerConfig(argv, fileConfig, {
|
|
327
373
|
port,
|
|
@@ -332,9 +378,9 @@ export class ServeCommandFactory {
|
|
|
332
378
|
spinner.text = `Creating ${framework} server...`;
|
|
333
379
|
}
|
|
334
380
|
// Create, register routes, initialize and start server
|
|
335
|
-
const serverRef = await ServeCommandFactory.createAndStartServer({ framework, serverConfig, basePath, port, host }, spinner);
|
|
381
|
+
const serverRef = await ServeCommandFactory.createAndStartServer({ framework, serverConfig, basePath, port, host, hardening }, spinner);
|
|
336
382
|
// Save state and print startup banner
|
|
337
|
-
ServeCommandFactory.saveAndPrintStartupInfo({ argv, port, host, framework, basePath, serverConfig }, spinner);
|
|
383
|
+
ServeCommandFactory.saveAndPrintStartupInfo({ argv, port, host, framework, basePath, serverConfig, hardening }, spinner);
|
|
338
384
|
// Set up watch mode if enabled
|
|
339
385
|
const stopWatcher = ServeCommandFactory.setupWatchMode(argv, {
|
|
340
386
|
serverRef,
|
|
@@ -343,6 +389,7 @@ export class ServeCommandFactory {
|
|
|
343
389
|
basePath,
|
|
344
390
|
port,
|
|
345
391
|
host,
|
|
392
|
+
hardening,
|
|
346
393
|
});
|
|
347
394
|
// Register signal handlers for graceful shutdown
|
|
348
395
|
ServeCommandFactory.registerSignalHandlers(serverRef, stopWatcher);
|
|
@@ -450,7 +497,7 @@ export class ServeCommandFactory {
|
|
|
450
497
|
*/
|
|
451
498
|
static async createAndStartServer(opts, spinner) {
|
|
452
499
|
const { createServer, registerAllRoutes } = await import("../../server/index.js");
|
|
453
|
-
const neurolink =
|
|
500
|
+
const neurolink = ServeCommandFactory.createServerNeuroLink(opts.hardening);
|
|
454
501
|
const serverRef = {
|
|
455
502
|
current: await createServer(neurolink, {
|
|
456
503
|
framework: opts.framework,
|
|
@@ -458,17 +505,33 @@ export class ServeCommandFactory {
|
|
|
458
505
|
}),
|
|
459
506
|
};
|
|
460
507
|
ServeCommandFactory.applyApiKeyAuth(serverRef.current, opts.basePath);
|
|
461
|
-
registerAllRoutes(serverRef.current, opts.basePath
|
|
508
|
+
registerAllRoutes(serverRef.current, opts.basePath, {
|
|
509
|
+
allowUnauthenticated: opts.hardening.allowUnauthenticated,
|
|
510
|
+
});
|
|
462
511
|
if (spinner) {
|
|
463
512
|
spinner.text = "Initializing server...";
|
|
464
513
|
}
|
|
465
514
|
await withTimeout(serverRef.current.initialize(), 30000, new ServerStartError("Server initialization timed out after 30 seconds", undefined, opts.port, opts.host));
|
|
466
515
|
await withTimeout(serverRef.current.start(), 30000, new ServerStartError("Server startup timed out after 30 seconds", undefined, opts.port, opts.host));
|
|
467
|
-
await attachTranscriptionWebSocket(serverRef.current, neurolink, opts.serverConfig);
|
|
516
|
+
await attachTranscriptionWebSocket(serverRef.current, neurolink, opts.serverConfig, opts.hardening);
|
|
468
517
|
return serverRef;
|
|
469
518
|
}
|
|
470
519
|
/**
|
|
471
|
-
*
|
|
520
|
+
* The NeuroLink instance the server runs. Its registry holds no built-in
|
|
521
|
+
* tool unless `--enable-builtin-tools`: those tools read and write files
|
|
522
|
+
* under the server's working directory, and `/tools/execute` and the agent
|
|
523
|
+
* routes would hand them to every caller. The switch is the SDK's own
|
|
524
|
+
* (NEUROLINK_DISABLE_BUILTIN_TOOLS), set before the instance and its
|
|
525
|
+
* providers read it, so generate() in this process does not see them either.
|
|
526
|
+
*/
|
|
527
|
+
static createServerNeuroLink(hardening) {
|
|
528
|
+
if (!hardening.enableBuiltinTools) {
|
|
529
|
+
process.env.NEUROLINK_DISABLE_BUILTIN_TOOLS = "true";
|
|
530
|
+
}
|
|
531
|
+
return new NeuroLink();
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Put every route except the health probes behind the API key(s) in
|
|
472
535
|
* NEUROLINK_SERVER_API_KEY. Must run before the routes are registered.
|
|
473
536
|
*/
|
|
474
537
|
static applyApiKeyAuth(server, basePath) {
|
|
@@ -495,7 +558,7 @@ export class ServeCommandFactory {
|
|
|
495
558
|
if (spinner) {
|
|
496
559
|
spinner.succeed(chalk.green("NeuroLink server started successfully"));
|
|
497
560
|
}
|
|
498
|
-
const url = `http://${opts.host === "0.0.0.0" ? "localhost" : opts.host}:${opts.port}`;
|
|
561
|
+
const url = `http://${opts.host === "0.0.0.0" || opts.host === "::" ? "localhost" : opts.host}:${opts.port}`;
|
|
499
562
|
const corsEnabled = opts.argv.cors ?? opts.serverConfig.cors?.enabled ?? true;
|
|
500
563
|
// The origin list and the API key together decide whether any browser
|
|
501
564
|
// page may call this server; the banner says which case this is.
|
|
@@ -507,6 +570,7 @@ export class ServeCommandFactory {
|
|
|
507
570
|
const swaggerEnabled = opts.argv.swagger ?? opts.serverConfig.enableSwagger ?? false;
|
|
508
571
|
ServeCommandFactory.printStartupBanner({
|
|
509
572
|
url,
|
|
573
|
+
host: opts.host,
|
|
510
574
|
framework: opts.framework,
|
|
511
575
|
basePath: opts.basePath,
|
|
512
576
|
pid: state.pid,
|
|
@@ -519,6 +583,8 @@ export class ServeCommandFactory {
|
|
|
519
583
|
swaggerEnabled,
|
|
520
584
|
watchEnabled: opts.argv.watch ?? false,
|
|
521
585
|
authEnabled: readServerApiKeys().length > 0,
|
|
586
|
+
allowUnauthenticated: opts.hardening.allowUnauthenticated === true,
|
|
587
|
+
builtinToolsEnabled: opts.hardening.enableBuiltinTools === true,
|
|
522
588
|
});
|
|
523
589
|
return state;
|
|
524
590
|
}
|
|
@@ -531,6 +597,11 @@ export class ServeCommandFactory {
|
|
|
531
597
|
logger.always(chalk.gray("=".repeat(50)));
|
|
532
598
|
logger.always("");
|
|
533
599
|
logger.always(` ${chalk.bold("URL:")} ${chalk.cyan(info.url)}`);
|
|
600
|
+
logger.always(` ${chalk.bold("Listening:")} ${isLoopbackHost(info.host)
|
|
601
|
+
? chalk.green(`${info.host} (this machine only)`)
|
|
602
|
+
: info.authEnabled
|
|
603
|
+
? chalk.green(`${info.host} (network; API key required)`)
|
|
604
|
+
: chalk.yellow(`${info.host} (network, NO authentication — every route is open to anyone who can reach this port)`)}`);
|
|
534
605
|
logger.always(` ${chalk.bold("Framework:")} ${chalk.cyan(info.framework)}`);
|
|
535
606
|
logger.always(` ${chalk.bold("Base Path:")} ${chalk.cyan(info.basePath)}`);
|
|
536
607
|
logger.always(` ${chalk.bold("PID:")} ${chalk.cyan(info.pid)}`);
|
|
@@ -554,7 +625,15 @@ export class ServeCommandFactory {
|
|
|
554
625
|
if (info.watchEnabled) {
|
|
555
626
|
logger.always(` Watch Mode: ${chalk.green("enabled")}`);
|
|
556
627
|
}
|
|
557
|
-
logger.always(` API Key: ${info.authEnabled ? chalk.green("required (health
|
|
628
|
+
logger.always(` API Key: ${info.authEnabled ? chalk.green("required (health probes exempt)") : chalk.yellow(`disabled — set ${SERVER_API_KEY_ENV} before exposing this server`)}`);
|
|
629
|
+
if (!info.authEnabled) {
|
|
630
|
+
logger.always(` Tool/MCP: ${info.allowUnauthenticated
|
|
631
|
+
? chalk.yellow("execution routes OPEN (--allow-unauthenticated)")
|
|
632
|
+
: chalk.green("execution routes answer 403 until a key is set")}`);
|
|
633
|
+
}
|
|
634
|
+
logger.always(` Built-in: ${info.builtinToolsEnabled
|
|
635
|
+
? chalk.yellow("tools enabled (file reads/writes under the working directory)")
|
|
636
|
+
: chalk.green("tools disabled (--enable-builtin-tools to allow)")}`);
|
|
558
637
|
logger.always("");
|
|
559
638
|
logger.always(chalk.bold("Available Endpoints:"));
|
|
560
639
|
logger.always(chalk.gray(" Health & Monitoring:"));
|
|
@@ -600,20 +679,22 @@ export class ServeCommandFactory {
|
|
|
600
679
|
const timestamp = Date.now();
|
|
601
680
|
const { createServer: createNewServer, registerAllRoutes: registerNewRoutes, } = await import(`../../server/index.js?t=${timestamp}`);
|
|
602
681
|
// Create new server
|
|
603
|
-
const newNeurolink =
|
|
682
|
+
const newNeurolink = ServeCommandFactory.createServerNeuroLink(ctx.hardening);
|
|
604
683
|
const newServer = await createNewServer(newNeurolink, {
|
|
605
684
|
framework: ctx.framework,
|
|
606
685
|
config: ctx.serverConfig,
|
|
607
686
|
});
|
|
608
687
|
ServeCommandFactory.applyApiKeyAuth(newServer, ctx.basePath);
|
|
609
|
-
registerNewRoutes(newServer, ctx.basePath
|
|
688
|
+
registerNewRoutes(newServer, ctx.basePath, {
|
|
689
|
+
allowUnauthenticated: ctx.hardening.allowUnauthenticated,
|
|
690
|
+
});
|
|
610
691
|
// Initialize and start with timeouts
|
|
611
692
|
await withTimeout(newServer.initialize(), 30000, new ServerStartError("Server initialization timed out during restart", undefined, ctx.port, ctx.host));
|
|
612
693
|
await withTimeout(newServer.start(), 30000, new ServerStartError("Server startup timed out during restart", undefined, ctx.port, ctx.host));
|
|
613
694
|
// The upgrade handler lives on the old adapter's http.Server; the
|
|
614
695
|
// restarted one needs its own, or the stream route is gone after the
|
|
615
696
|
// first file change.
|
|
616
|
-
await attachTranscriptionWebSocket(newServer, newNeurolink, ctx.serverConfig);
|
|
697
|
+
await attachTranscriptionWebSocket(newServer, newNeurolink, ctx.serverConfig, ctx.hardening);
|
|
617
698
|
// Update the reference so signal handlers use the new server instance
|
|
618
699
|
ctx.serverRef.current = newServer;
|
|
619
700
|
logger.always(chalk.green("Server restarted successfully"));
|
|
@@ -709,7 +790,7 @@ export class ServeCommandFactory {
|
|
|
709
790
|
status.startTime = state.startTime;
|
|
710
791
|
status.uptime = Date.now() - new Date(state.startTime).getTime();
|
|
711
792
|
status.configFile = state.configFile ?? null;
|
|
712
|
-
status.url = `http://${state.host === "0.0.0.0" ? "localhost" : state.host}:${state.port}`;
|
|
793
|
+
status.url = `http://${state.host === "0.0.0.0" || state.host === "::" ? "localhost" : state.host}:${state.port}`;
|
|
713
794
|
}
|
|
714
795
|
if (argv.format === "json") {
|
|
715
796
|
logger.always(JSON.stringify(status, null, 2));
|
|
@@ -9,7 +9,7 @@ import path from "path";
|
|
|
9
9
|
import { logger } from "../../utils/logger.js";
|
|
10
10
|
import { NeuroLink } from "../../neurolink.js";
|
|
11
11
|
import { withTimeout } from "../../utils/errorHandling.js";
|
|
12
|
-
import {
|
|
12
|
+
import { DEFAULT_SERVER_HOST, SERVER_ALLOW_UNAUTHENTICATED_ENV, SERVER_API_KEY_ENV, clearStateOnExit, createServerApiKeyMiddleware, ensureStateDir, formatUptime, getNeuroLinkDir, isOtherServerRunning, isProcessRunning, readServerApiKeys, resolveServerPort, unauthenticatedBindRefusal, } from "../utils/serverUtils.js";
|
|
13
13
|
// ============================================
|
|
14
14
|
// State Management
|
|
15
15
|
// ============================================
|
|
@@ -47,7 +47,7 @@ const CONFIG_FILE = path.join(getNeuroLinkDir(), "server-config.json");
|
|
|
47
47
|
function loadServerConfig() {
|
|
48
48
|
const defaults = {
|
|
49
49
|
defaultPort: 3000,
|
|
50
|
-
defaultHost:
|
|
50
|
+
defaultHost: DEFAULT_SERVER_HOST,
|
|
51
51
|
defaultFramework: "hono",
|
|
52
52
|
defaultBasePath: "/api",
|
|
53
53
|
cors: { enabled: true },
|
|
@@ -165,7 +165,7 @@ export class ServerCommandFactory {
|
|
|
165
165
|
.option("host", {
|
|
166
166
|
type: "string",
|
|
167
167
|
alias: "H",
|
|
168
|
-
default:
|
|
168
|
+
default: DEFAULT_SERVER_HOST,
|
|
169
169
|
description: "Host to bind to",
|
|
170
170
|
})
|
|
171
171
|
.option("framework", {
|
|
@@ -356,12 +356,24 @@ export class ServerCommandFactory {
|
|
|
356
356
|
const { createServer, registerAllRoutes } = await import("../../server/index.js");
|
|
357
357
|
const port = resolveServerPort(argv.port);
|
|
358
358
|
const framework = (argv.framework ?? "hono");
|
|
359
|
+
// A listener the network can reach with no key is refused unless the
|
|
360
|
+
// operator asked for exactly that (NEUROLINK_SERVER_ALLOW_UNAUTHENTICATED=1).
|
|
361
|
+
const bindHost = argv.host ?? DEFAULT_SERVER_HOST;
|
|
362
|
+
const refusal = unauthenticatedBindRefusal({
|
|
363
|
+
host: bindHost,
|
|
364
|
+
apiKeyCount: readServerApiKeys().length,
|
|
365
|
+
allowUnauthenticated: process.env[SERVER_ALLOW_UNAUTHENTICATED_ENV] === "1",
|
|
366
|
+
});
|
|
367
|
+
if (refusal) {
|
|
368
|
+
logger.error(chalk.red(refusal));
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
359
371
|
// Create server
|
|
360
372
|
const server = await createServer(neurolink, {
|
|
361
373
|
framework,
|
|
362
374
|
config: {
|
|
363
375
|
port,
|
|
364
|
-
host:
|
|
376
|
+
host: bindHost,
|
|
365
377
|
basePath: argv.basePath ?? "/api",
|
|
366
378
|
cors: {
|
|
367
379
|
enabled: argv.cors ?? true,
|
|
@@ -386,7 +398,7 @@ export class ServerCommandFactory {
|
|
|
386
398
|
const state = {
|
|
387
399
|
pid: process.pid,
|
|
388
400
|
port,
|
|
389
|
-
host: argv.host ??
|
|
401
|
+
host: argv.host ?? DEFAULT_SERVER_HOST,
|
|
390
402
|
framework,
|
|
391
403
|
startTime: new Date().toISOString(),
|
|
392
404
|
basePath: argv.basePath ?? "/api",
|
package/dist/cli/index.js
CHANGED
|
@@ -7,10 +7,14 @@ process.env.DOTENV_CONFIG_QUIET = "true";
|
|
|
7
7
|
* Professional CLI experience with minimal maintenance overhead.
|
|
8
8
|
* Features: Spinners, colors, batch processing, provider testing, rich help
|
|
9
9
|
*/
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import * as path from "node:path";
|
|
10
12
|
import { initializeCliParser } from "./parser.js";
|
|
11
13
|
import chalk from "chalk";
|
|
12
14
|
import { initializeCliLifecycle, cleanupCliLifecycle } from "./lifecycle.js";
|
|
13
15
|
import { loadProcessDotenv } from "../utils/dotenvBootstrap.js";
|
|
16
|
+
import { ensureStdinRef } from "./utils/stdinRef.js";
|
|
17
|
+
import { PROJECT_MCP_CONFIG_FILENAME, TRUST_MCP_CONFIG_ENV, TRUST_MCP_CONFIG_FLAG, formatProjectMcpConfigSummary, getProjectMcpConfigTrustStatus, getTrustedMcpConfigStorePath, recordTrustedProjectMcpConfig, setProjectMcpConfigTrustHook, summarizeProjectMcpConfig, } from "../mcp/projectConfigTrust.js";
|
|
14
18
|
// Clean up pnpm-specific environment variables that cause npm warnings
|
|
15
19
|
// These variables are set by pnpm but cause "Unknown env config" warnings in npm
|
|
16
20
|
if (process.env.npm_config_verify_deps_before_run) {
|
|
@@ -19,11 +23,148 @@ if (process.env.npm_config_verify_deps_before_run) {
|
|
|
19
23
|
if (process.env.npm_config__jsr_registry) {
|
|
20
24
|
delete process.env.npm_config__jsr_registry;
|
|
21
25
|
}
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
// `--trust-mcp-config` is a global flag consumed here, before yargs sees the
|
|
27
|
+
// argument vector: the parser runs with `strictOptions()`, so an option it
|
|
28
|
+
// does not know about would otherwise be rejected on every command.
|
|
29
|
+
const trustMcpConfigFlag = process.argv.includes(TRUST_MCP_CONFIG_FLAG);
|
|
30
|
+
if (trustMcpConfigFlag) {
|
|
31
|
+
process.argv = process.argv.filter((argument) => argument !== TRUST_MCP_CONFIG_FLAG);
|
|
32
|
+
}
|
|
33
|
+
// Load environment variables from `.env`. The CLI is the one entry point
|
|
34
|
+
// that still does this implicitly — a terminal session in a project directory
|
|
35
|
+
// is what the file is for. Endpoint-override variables are refused unless the
|
|
36
|
+
// file is owned by this user and not group/world-writable; see
|
|
37
|
+
// dotenvBootstrap.ts. `NEUROLINK_DOTENV=0` disables the load entirely.
|
|
38
|
+
await loadProcessDotenv({ caller: "cli" });
|
|
39
|
+
function warn(message) {
|
|
40
|
+
process.stderr.write(chalk.yellow(`${message}\n`));
|
|
41
|
+
}
|
|
42
|
+
function isHelpOrVersionInvocation(args) {
|
|
43
|
+
if (args.length === 0) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
return args.some((argument) => argument === "--help" ||
|
|
47
|
+
argument === "-h" ||
|
|
48
|
+
argument === "help" ||
|
|
49
|
+
argument === "--version" ||
|
|
50
|
+
argument === "-V" ||
|
|
51
|
+
argument === "version");
|
|
52
|
+
}
|
|
53
|
+
function describeUntrusted(summary, status) {
|
|
54
|
+
return [
|
|
55
|
+
status === "changed"
|
|
56
|
+
? `This project's ${PROJECT_MCP_CONFIG_FILENAME} has changed since you last trusted it:`
|
|
57
|
+
: `This project has a ${PROJECT_MCP_CONFIG_FILENAME} you have not trusted yet:`,
|
|
58
|
+
...formatProjectMcpConfigSummary(summary),
|
|
59
|
+
"Loading it starts the commands and connections above with your environment.",
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
function howToDecide() {
|
|
63
|
+
return [
|
|
64
|
+
"To continue, either:",
|
|
65
|
+
` • review the file, then re-run with ${TRUST_MCP_CONFIG_FLAG} (or ${TRUST_MCP_CONFIG_ENV}=1) to trust exactly this content — the hash is remembered in ${getTrustedMcpConfigStorePath()}`,
|
|
66
|
+
` • or set ${TRUST_MCP_CONFIG_ENV}=0 to run without it`,
|
|
67
|
+
" • or pass --disable-tools so no MCP server is needed",
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
async function promptForTrust(summary, status) {
|
|
71
|
+
ensureStdinRef();
|
|
72
|
+
const readline = await import("node:readline/promises");
|
|
73
|
+
const rl = readline.createInterface({
|
|
74
|
+
input: process.stdin,
|
|
75
|
+
output: process.stderr,
|
|
76
|
+
});
|
|
77
|
+
try {
|
|
78
|
+
process.stderr.write(chalk.yellow(`${describeUntrusted(summary, status).join("\n")}\n`));
|
|
79
|
+
const answer = await rl.question("Trust this file and start these servers? [y/N] ");
|
|
80
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
rl.close();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Decide, once per invocation, what to do about the working directory's
|
|
88
|
+
* `.mcp-config.json`, and hand that decision to the SDK through its trust
|
|
89
|
+
* hook. The hook only runs when a command actually initialises MCP, so a
|
|
90
|
+
* command that never needs a tool is unaffected, and the decision that does
|
|
91
|
+
* refuse happens before any server is started.
|
|
92
|
+
*
|
|
93
|
+
* A file already recorded in `~/.neurolink/trusted-mcp-configs.json` with the
|
|
94
|
+
* same hash loads silently. Otherwise `--trust-mcp-config` /
|
|
95
|
+
* `NEUROLINK_TRUST_MCP_CONFIG=1` accepts and records it, `=0` ignores it, an
|
|
96
|
+
* interactive terminal is asked, and a non-interactive run that was told
|
|
97
|
+
* nothing refuses with exit status 1 at the moment the file would load.
|
|
98
|
+
*/
|
|
99
|
+
async function installProjectMcpConfigGate() {
|
|
100
|
+
if (isHelpOrVersionInvocation(process.argv.slice(2))) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const candidate = path.join(process.cwd(), PROJECT_MCP_CONFIG_FILENAME);
|
|
104
|
+
if (!existsSync(candidate)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const envValue = process.env[TRUST_MCP_CONFIG_ENV];
|
|
108
|
+
if (envValue === "0" || envValue === "false") {
|
|
109
|
+
setProjectMcpConfigTrustHook(async () => {
|
|
110
|
+
warn(`[neurolink] Ignoring ${candidate} (${TRUST_MCP_CONFIG_ENV}=${envValue}).`);
|
|
111
|
+
return null;
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
let summary;
|
|
116
|
+
try {
|
|
117
|
+
summary = summarizeProjectMcpConfig(candidate);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
setProjectMcpConfigTrustHook(async () => {
|
|
121
|
+
warn(`[neurolink] Not loading ${candidate}: it could not be read (${error instanceof Error ? error.message : String(error)}).`);
|
|
122
|
+
return null;
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Nothing to run, nothing to trust: let the loader report the parse error
|
|
127
|
+
// or the empty server list itself.
|
|
128
|
+
if (summary.parseError !== undefined || summary.servers.length === 0) {
|
|
129
|
+
setProjectMcpConfigTrustHook(async () => summary.path);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const status = getProjectMcpConfigTrustStatus(summary.path, summary.sha256);
|
|
133
|
+
if (status === "trusted") {
|
|
134
|
+
setProjectMcpConfigTrustHook(async () => summary.path);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const accept = () => {
|
|
138
|
+
try {
|
|
139
|
+
recordTrustedProjectMcpConfig(summary.path, summary.sha256);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
warn(`[neurolink] Trusted ${summary.path} for this run, but could not record it in ${getTrustedMcpConfigStorePath()} (${error instanceof Error ? error.message : String(error)}).`);
|
|
143
|
+
}
|
|
144
|
+
setProjectMcpConfigTrustHook(async () => summary.path);
|
|
145
|
+
};
|
|
146
|
+
if (trustMcpConfigFlag || envValue === "1" || envValue === "true") {
|
|
147
|
+
accept();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const interactive = Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
151
|
+
if (interactive) {
|
|
152
|
+
if (await promptForTrust(summary, status)) {
|
|
153
|
+
accept();
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
warn(`[neurolink] Not loading ${summary.path}. Re-run with ${TRUST_MCP_CONFIG_FLAG} once you have reviewed it.`);
|
|
157
|
+
setProjectMcpConfigTrustHook(async () => null);
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
setProjectMcpConfigTrustHook(async () => {
|
|
162
|
+
process.stderr.write(chalk.red(`neurolink: refusing to load an untrusted project MCP configuration.\n`));
|
|
163
|
+
process.stderr.write(`${[...describeUntrusted(summary, status), ...howToDecide()].join("\n")}\n`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
await installProjectMcpConfigGate();
|
|
27
168
|
// Enhanced CLI with Professional UX
|
|
28
169
|
const cli = initializeCliParser();
|
|
29
170
|
initializeCliLifecycle(async () => {
|
|
@@ -4,4 +4,4 @@ import type { OptionSchema, TextGenerationOptions } from "../../types/index.js";
|
|
|
4
4
|
* This object provides metadata for validation and help text in the CLI loop.
|
|
5
5
|
* It is derived from the main TextGenerationOptions interface to ensure consistency.
|
|
6
6
|
*/
|
|
7
|
-
export declare const textGenerationOptionsSchema: Record<keyof Omit<TextGenerationOptions, "prompt" | "input" | "schema" | "tools" | "context" | "conversationHistory" | "conversationMessages" | "conversationMemoryConfig" | "originalPrompt" | "middleware" | "expectedOutcome" | "evaluationCriteria" | "region" | "csvOptions" | "pdfOptions" | "imageOptions" | "audioOptions" | "videoOptions" | "officeOptions" | "tts" | "stt" | "thinkingConfig" | "requestId" | "fileRegistry" | "abortSignal" | "toolFilter" | "excludeTools" | "toolChoice" | "prepareStep" | "credentials" | "onFinish" | "onError" | "processors" | "piiDetection" | "responseValidation" | "inputValidation" | "toolExecutionCapture" | "toolExecutionRecorder" | "toolRoots">, OptionSchema>;
|
|
7
|
+
export declare const textGenerationOptionsSchema: Record<keyof Omit<TextGenerationOptions, "prompt" | "input" | "schema" | "tools" | "context" | "conversationHistory" | "conversationMessages" | "conversationMemoryConfig" | "originalPrompt" | "middleware" | "expectedOutcome" | "evaluationCriteria" | "region" | "csvOptions" | "pdfOptions" | "imageOptions" | "audioOptions" | "videoOptions" | "officeOptions" | "tts" | "stt" | "thinkingConfig" | "requestId" | "fileRegistry" | "attachmentPolicy" | "abortSignal" | "toolFilter" | "excludeTools" | "toolChoice" | "prepareStep" | "credentials" | "onFinish" | "onError" | "processors" | "piiDetection" | "responseValidation" | "inputValidation" | "toolExecutionCapture" | "toolExecutionRecorder" | "toolRoots">, OptionSchema>;
|
|
@@ -22,7 +22,13 @@ export function backupEnvFile(envPath = ".env") {
|
|
|
22
22
|
.split(".")[0]; // Remove milliseconds
|
|
23
23
|
const backupPath = `${envPath}.backup.${timestamp}`;
|
|
24
24
|
try {
|
|
25
|
-
|
|
25
|
+
// The backup holds the same provider keys as `.env`: owner-only, and
|
|
26
|
+
// never over an existing file (a second backup in the same second gets
|
|
27
|
+
// a fresh error rather than a silent overwrite).
|
|
28
|
+
fs.writeFileSync(backupPath, fs.readFileSync(envPath), {
|
|
29
|
+
mode: 0o600,
|
|
30
|
+
flag: "wx",
|
|
31
|
+
});
|
|
26
32
|
result.backupPath = backupPath;
|
|
27
33
|
}
|
|
28
34
|
catch (error) {
|
|
@@ -153,9 +159,17 @@ export function updateEnvFile(newVars, envPath = ".env", createBackup = true, ke
|
|
|
153
159
|
}
|
|
154
160
|
// Generate new content
|
|
155
161
|
const newContent = generateEnvContent(newVars, existingContent, keysToDelete);
|
|
156
|
-
// Write updated file
|
|
162
|
+
// Write updated file, owner-only. `mode` applies only when the file is
|
|
163
|
+
// created, so an existing `.env` is tightened explicitly as well; a
|
|
164
|
+
// platform without POSIX modes (Windows) ignores the chmod.
|
|
157
165
|
try {
|
|
158
|
-
fs.writeFileSync(envPath, newContent, "utf8");
|
|
166
|
+
fs.writeFileSync(envPath, newContent, { encoding: "utf8", mode: 0o600 });
|
|
167
|
+
try {
|
|
168
|
+
fs.chmodSync(envPath, 0o600);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Not a POSIX file system; the write itself succeeded.
|
|
172
|
+
}
|
|
159
173
|
}
|
|
160
174
|
catch (error) {
|
|
161
175
|
throw new Error(`Failed to write .env file: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
@@ -2,10 +2,43 @@
|
|
|
2
2
|
* Server Utilities for NeuroLink CLI
|
|
3
3
|
* Shared utility functions for server management commands (serve.ts and server.ts)
|
|
4
4
|
*/
|
|
5
|
+
import { DEFAULT_SERVER_HOST } from "../../server/abstract/baseServerAdapter.js";
|
|
5
6
|
import type { MiddlewareDefinition } from "../../types/index.js";
|
|
6
7
|
export declare const DEFAULT_SERVER_PORT = 3000;
|
|
7
|
-
/**
|
|
8
|
+
/** The interface `neurolink serve` binds unless told otherwise: this host only. */
|
|
9
|
+
export { DEFAULT_SERVER_HOST };
|
|
10
|
+
/** Comma-separated API keys; when set, every route except the health probes needs one. */
|
|
8
11
|
export declare const SERVER_API_KEY_ENV = "NEUROLINK_SERVER_API_KEY";
|
|
12
|
+
/**
|
|
13
|
+
* `=1` lets `neurolink serve` listen on a non-loopback host with no API key,
|
|
14
|
+
* and serves the tool and MCP execution routes to anyone who can reach it.
|
|
15
|
+
*/
|
|
16
|
+
export declare const SERVER_ALLOW_UNAUTHENTICATED_ENV = "NEUROLINK_SERVER_ALLOW_UNAUTHENTICATED";
|
|
17
|
+
/** `=1` keeps the built-in tools (filesystem among them) in the server's registry. */
|
|
18
|
+
export declare const SERVER_ENABLE_BUILTIN_TOOLS_ENV = "NEUROLINK_SERVER_ENABLE_BUILTIN_TOOLS";
|
|
19
|
+
/** `=1` lets the transcription WebSocket take the API key as `?token=`. */
|
|
20
|
+
export declare const SERVER_WS_ALLOW_QUERY_TOKEN_ENV = "NEUROLINK_SERVER_WS_ALLOW_QUERY_TOKEN";
|
|
21
|
+
/** Whether `name` is set to `1` (trimmed) in the environment. */
|
|
22
|
+
export declare function envFlag(name: string): boolean;
|
|
23
|
+
/** `true` when `host` names only this machine (`127.0.0.1`, `::1`, `localhost`). */
|
|
24
|
+
export declare function isLoopbackHost(host: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Why `neurolink serve` must not start as asked, or `undefined` when it may.
|
|
27
|
+
* A listener the network can reach with no API key hands every route, tool
|
|
28
|
+
* execution included, to anyone who finds the port; the operator has to say so.
|
|
29
|
+
*/
|
|
30
|
+
export declare function unauthenticatedBindRefusal(options: {
|
|
31
|
+
host: string;
|
|
32
|
+
apiKeyCount: number;
|
|
33
|
+
allowUnauthenticated: boolean;
|
|
34
|
+
}): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* The paths a key never guards: the liveness, readiness and startup probes an
|
|
37
|
+
* orchestrator calls with no credentials, and the basic health check. An
|
|
38
|
+
* exact list: `/health/detailed` reports the runtime and the MCP inventory,
|
|
39
|
+
* and `/version` the Node version, so both need the key like any other route.
|
|
40
|
+
*/
|
|
41
|
+
export declare function healthExemptPaths(basePath: string): string[];
|
|
9
42
|
/**
|
|
10
43
|
* Resolve the listen port: --port, then the config file, then the PORT
|
|
11
44
|
* environment variable (what container platforms set), then 3000.
|
|
@@ -14,8 +47,11 @@ export declare function resolveServerPort(cliPort?: number, filePort?: number):
|
|
|
14
47
|
export declare function readServerApiKeys(): string[];
|
|
15
48
|
/**
|
|
16
49
|
* Require one of `apiKeys` as `Authorization: Bearer <key>` or `X-API-Key`.
|
|
17
|
-
*
|
|
18
|
-
* playground header cannot bypass a key the operator
|
|
50
|
+
* The health probes (see `healthExemptPaths`) stay open so orchestrators can
|
|
51
|
+
* reach them, and the dev playground header cannot bypass a key the operator
|
|
52
|
+
* configured. The exemption is an exact match on the path, made here rather
|
|
53
|
+
* than through the adapters' `excludePaths`, whose entries cover a subtree:
|
|
54
|
+
* `/health` used to exempt `/health/detailed` and even `/health/api/version`.
|
|
19
55
|
*
|
|
20
56
|
* Every holder of a key gets the same `{ id: "api-key" }`, marked `shared` so
|
|
21
57
|
* the agent routes know it names no person and do not warn when a request
|