@juspay/neurolink 9.40.0 → 9.42.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 (224) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +7 -1
  3. package/dist/auth/anthropicOAuth.d.ts +18 -3
  4. package/dist/auth/anthropicOAuth.js +137 -4
  5. package/dist/auth/providers/firebase.js +5 -1
  6. package/dist/auth/providers/jwt.js +5 -1
  7. package/dist/auth/providers/workos.js +5 -1
  8. package/dist/auth/sessionManager.d.ts +1 -1
  9. package/dist/auth/sessionManager.js +58 -27
  10. package/dist/browser/neurolink.min.js +471 -445
  11. package/dist/cli/commands/mcp.js +3 -0
  12. package/dist/cli/commands/proxy.d.ts +2 -1
  13. package/dist/cli/commands/proxy.js +279 -16
  14. package/dist/cli/commands/task.d.ts +56 -0
  15. package/dist/cli/commands/task.js +838 -0
  16. package/dist/cli/factories/commandFactory.d.ts +2 -0
  17. package/dist/cli/factories/commandFactory.js +38 -0
  18. package/dist/cli/parser.js +8 -4
  19. package/dist/client/aiSdkAdapter.js +3 -0
  20. package/dist/client/streamingClient.js +30 -10
  21. package/dist/core/modules/GenerationHandler.js +3 -2
  22. package/dist/core/redisConversationMemoryManager.js +7 -3
  23. package/dist/evaluation/BatchEvaluator.js +4 -1
  24. package/dist/evaluation/hooks/observabilityHooks.js +5 -3
  25. package/dist/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  26. package/dist/evaluation/pipeline/evaluationPipeline.js +20 -8
  27. package/dist/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  28. package/dist/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  29. package/dist/lib/auth/anthropicOAuth.d.ts +18 -3
  30. package/dist/lib/auth/anthropicOAuth.js +137 -4
  31. package/dist/lib/auth/providers/firebase.js +5 -1
  32. package/dist/lib/auth/providers/jwt.js +5 -1
  33. package/dist/lib/auth/providers/workos.js +5 -1
  34. package/dist/lib/auth/sessionManager.d.ts +1 -1
  35. package/dist/lib/auth/sessionManager.js +58 -27
  36. package/dist/lib/client/aiSdkAdapter.js +3 -0
  37. package/dist/lib/client/streamingClient.js +30 -10
  38. package/dist/lib/core/modules/GenerationHandler.js +3 -2
  39. package/dist/lib/core/redisConversationMemoryManager.js +7 -3
  40. package/dist/lib/evaluation/BatchEvaluator.js +4 -1
  41. package/dist/lib/evaluation/hooks/observabilityHooks.js +5 -3
  42. package/dist/lib/evaluation/pipeline/evaluationPipeline.d.ts +3 -2
  43. package/dist/lib/evaluation/pipeline/evaluationPipeline.js +20 -8
  44. package/dist/lib/evaluation/pipeline/strategies/batchStrategy.js +6 -3
  45. package/dist/lib/evaluation/pipeline/strategies/samplingStrategy.js +18 -10
  46. package/dist/lib/neurolink.d.ts +18 -1
  47. package/dist/lib/neurolink.js +367 -484
  48. package/dist/lib/observability/otelBridge.d.ts +2 -2
  49. package/dist/lib/observability/otelBridge.js +12 -3
  50. package/dist/lib/providers/amazonBedrock.js +2 -4
  51. package/dist/lib/providers/anthropic.d.ts +9 -5
  52. package/dist/lib/providers/anthropic.js +19 -14
  53. package/dist/lib/providers/anthropicBaseProvider.d.ts +3 -3
  54. package/dist/lib/providers/anthropicBaseProvider.js +5 -4
  55. package/dist/lib/providers/azureOpenai.d.ts +1 -1
  56. package/dist/lib/providers/azureOpenai.js +5 -4
  57. package/dist/lib/providers/googleAiStudio.js +30 -1
  58. package/dist/lib/providers/googleVertex.js +28 -6
  59. package/dist/lib/providers/huggingFace.d.ts +3 -3
  60. package/dist/lib/providers/huggingFace.js +6 -8
  61. package/dist/lib/providers/litellm.js +41 -29
  62. package/dist/lib/providers/mistral.js +2 -1
  63. package/dist/lib/providers/ollama.js +80 -23
  64. package/dist/lib/providers/openAI.js +3 -2
  65. package/dist/lib/providers/openRouter.js +2 -1
  66. package/dist/lib/providers/openaiCompatible.d.ts +4 -4
  67. package/dist/lib/providers/openaiCompatible.js +4 -4
  68. package/dist/lib/proxy/claudeFormat.d.ts +3 -2
  69. package/dist/lib/proxy/claudeFormat.js +25 -20
  70. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  71. package/dist/lib/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  72. package/dist/lib/proxy/modelRouter.js +3 -0
  73. package/dist/lib/proxy/oauthFetch.d.ts +1 -1
  74. package/dist/lib/proxy/oauthFetch.js +65 -72
  75. package/dist/lib/proxy/proxyConfig.js +44 -24
  76. package/dist/lib/proxy/proxyEnv.d.ts +19 -0
  77. package/dist/lib/proxy/proxyEnv.js +73 -0
  78. package/dist/lib/proxy/proxyFetch.js +50 -4
  79. package/dist/lib/proxy/proxyTracer.d.ts +133 -0
  80. package/dist/lib/proxy/proxyTracer.js +645 -0
  81. package/dist/lib/proxy/rawStreamCapture.d.ts +10 -0
  82. package/dist/lib/proxy/rawStreamCapture.js +83 -0
  83. package/dist/lib/proxy/requestLogger.d.ts +32 -5
  84. package/dist/lib/proxy/requestLogger.js +406 -37
  85. package/dist/lib/proxy/sseInterceptor.d.ts +97 -0
  86. package/dist/lib/proxy/sseInterceptor.js +402 -0
  87. package/dist/lib/proxy/usageStats.d.ts +4 -3
  88. package/dist/lib/proxy/usageStats.js +25 -12
  89. package/dist/lib/rag/chunkers/MarkdownChunker.js +13 -5
  90. package/dist/lib/rag/chunking/markdownChunker.js +15 -6
  91. package/dist/lib/server/routes/claudeProxyRoutes.d.ts +7 -2
  92. package/dist/lib/server/routes/claudeProxyRoutes.js +1737 -508
  93. package/dist/lib/services/server/ai/observability/instrumentation.d.ts +7 -1
  94. package/dist/lib/services/server/ai/observability/instrumentation.js +240 -40
  95. package/dist/lib/tasks/backends/bullmqBackend.d.ts +33 -0
  96. package/dist/lib/tasks/backends/bullmqBackend.js +196 -0
  97. package/dist/lib/tasks/backends/nodeTimeoutBackend.d.ts +27 -0
  98. package/dist/lib/tasks/backends/nodeTimeoutBackend.js +141 -0
  99. package/dist/lib/tasks/backends/taskBackendRegistry.d.ts +31 -0
  100. package/dist/lib/tasks/backends/taskBackendRegistry.js +66 -0
  101. package/dist/lib/tasks/errors.d.ts +31 -0
  102. package/dist/lib/tasks/errors.js +18 -0
  103. package/dist/lib/tasks/store/fileTaskStore.d.ts +43 -0
  104. package/dist/lib/tasks/store/fileTaskStore.js +179 -0
  105. package/dist/lib/tasks/store/redisTaskStore.d.ts +43 -0
  106. package/dist/lib/tasks/store/redisTaskStore.js +197 -0
  107. package/dist/lib/tasks/taskExecutor.d.ts +21 -0
  108. package/dist/lib/tasks/taskExecutor.js +166 -0
  109. package/dist/lib/tasks/taskManager.d.ts +63 -0
  110. package/dist/lib/tasks/taskManager.js +426 -0
  111. package/dist/lib/tasks/tools/taskTools.d.ts +135 -0
  112. package/dist/lib/tasks/tools/taskTools.js +274 -0
  113. package/dist/lib/telemetry/index.d.ts +2 -1
  114. package/dist/lib/telemetry/index.js +2 -1
  115. package/dist/lib/telemetry/telemetryService.d.ts +3 -0
  116. package/dist/lib/telemetry/telemetryService.js +65 -5
  117. package/dist/lib/types/cli.d.ts +10 -0
  118. package/dist/lib/types/configTypes.d.ts +3 -0
  119. package/dist/lib/types/generateTypes.d.ts +13 -0
  120. package/dist/lib/types/index.d.ts +1 -0
  121. package/dist/lib/types/proxyTypes.d.ts +37 -5
  122. package/dist/lib/types/streamTypes.d.ts +25 -3
  123. package/dist/lib/types/taskTypes.d.ts +275 -0
  124. package/dist/lib/types/taskTypes.js +37 -0
  125. package/dist/lib/utils/messageBuilder.js +3 -2
  126. package/dist/lib/utils/providerHealth.d.ts +18 -0
  127. package/dist/lib/utils/providerHealth.js +240 -9
  128. package/dist/lib/utils/providerUtils.js +14 -8
  129. package/dist/lib/utils/toolChoice.d.ts +4 -0
  130. package/dist/lib/utils/toolChoice.js +7 -0
  131. package/dist/neurolink.d.ts +18 -1
  132. package/dist/neurolink.js +367 -484
  133. package/dist/observability/otelBridge.d.ts +2 -2
  134. package/dist/observability/otelBridge.js +12 -3
  135. package/dist/providers/amazonBedrock.js +2 -4
  136. package/dist/providers/anthropic.d.ts +9 -5
  137. package/dist/providers/anthropic.js +19 -14
  138. package/dist/providers/anthropicBaseProvider.d.ts +3 -3
  139. package/dist/providers/anthropicBaseProvider.js +5 -4
  140. package/dist/providers/azureOpenai.d.ts +1 -1
  141. package/dist/providers/azureOpenai.js +5 -4
  142. package/dist/providers/googleAiStudio.js +30 -1
  143. package/dist/providers/googleVertex.js +28 -6
  144. package/dist/providers/huggingFace.d.ts +3 -3
  145. package/dist/providers/huggingFace.js +6 -7
  146. package/dist/providers/litellm.js +41 -29
  147. package/dist/providers/mistral.js +2 -1
  148. package/dist/providers/ollama.js +80 -23
  149. package/dist/providers/openAI.js +3 -2
  150. package/dist/providers/openRouter.js +2 -1
  151. package/dist/providers/openaiCompatible.d.ts +4 -4
  152. package/dist/providers/openaiCompatible.js +4 -3
  153. package/dist/proxy/claudeFormat.d.ts +3 -2
  154. package/dist/proxy/claudeFormat.js +25 -20
  155. package/dist/proxy/cloaking/plugins/sessionIdentity.d.ts +2 -6
  156. package/dist/proxy/cloaking/plugins/sessionIdentity.js +9 -33
  157. package/dist/proxy/modelRouter.js +3 -0
  158. package/dist/proxy/oauthFetch.d.ts +1 -1
  159. package/dist/proxy/oauthFetch.js +65 -72
  160. package/dist/proxy/proxyConfig.js +44 -24
  161. package/dist/proxy/proxyEnv.d.ts +19 -0
  162. package/dist/proxy/proxyEnv.js +72 -0
  163. package/dist/proxy/proxyFetch.js +50 -4
  164. package/dist/proxy/proxyTracer.d.ts +133 -0
  165. package/dist/proxy/proxyTracer.js +644 -0
  166. package/dist/proxy/rawStreamCapture.d.ts +10 -0
  167. package/dist/proxy/rawStreamCapture.js +82 -0
  168. package/dist/proxy/requestLogger.d.ts +32 -5
  169. package/dist/proxy/requestLogger.js +406 -37
  170. package/dist/proxy/sseInterceptor.d.ts +97 -0
  171. package/dist/proxy/sseInterceptor.js +401 -0
  172. package/dist/proxy/usageStats.d.ts +4 -3
  173. package/dist/proxy/usageStats.js +25 -12
  174. package/dist/rag/chunkers/MarkdownChunker.js +13 -5
  175. package/dist/rag/chunking/markdownChunker.js +15 -6
  176. package/dist/server/routes/claudeProxyRoutes.d.ts +7 -2
  177. package/dist/server/routes/claudeProxyRoutes.js +1737 -508
  178. package/dist/services/server/ai/observability/instrumentation.d.ts +7 -1
  179. package/dist/services/server/ai/observability/instrumentation.js +240 -40
  180. package/dist/tasks/backends/bullmqBackend.d.ts +33 -0
  181. package/dist/tasks/backends/bullmqBackend.js +195 -0
  182. package/dist/tasks/backends/nodeTimeoutBackend.d.ts +27 -0
  183. package/dist/tasks/backends/nodeTimeoutBackend.js +140 -0
  184. package/dist/tasks/backends/taskBackendRegistry.d.ts +31 -0
  185. package/dist/tasks/backends/taskBackendRegistry.js +65 -0
  186. package/dist/tasks/errors.d.ts +31 -0
  187. package/dist/tasks/errors.js +17 -0
  188. package/dist/tasks/store/fileTaskStore.d.ts +43 -0
  189. package/dist/tasks/store/fileTaskStore.js +178 -0
  190. package/dist/tasks/store/redisTaskStore.d.ts +43 -0
  191. package/dist/tasks/store/redisTaskStore.js +196 -0
  192. package/dist/tasks/taskExecutor.d.ts +21 -0
  193. package/dist/tasks/taskExecutor.js +165 -0
  194. package/dist/tasks/taskManager.d.ts +63 -0
  195. package/dist/tasks/taskManager.js +425 -0
  196. package/dist/tasks/tools/taskTools.d.ts +135 -0
  197. package/dist/tasks/tools/taskTools.js +273 -0
  198. package/dist/telemetry/index.d.ts +2 -1
  199. package/dist/telemetry/index.js +2 -1
  200. package/dist/telemetry/telemetryService.d.ts +3 -0
  201. package/dist/telemetry/telemetryService.js +65 -5
  202. package/dist/types/cli.d.ts +10 -0
  203. package/dist/types/configTypes.d.ts +3 -0
  204. package/dist/types/generateTypes.d.ts +13 -0
  205. package/dist/types/index.d.ts +1 -0
  206. package/dist/types/proxyTypes.d.ts +37 -5
  207. package/dist/types/streamTypes.d.ts +25 -3
  208. package/dist/types/taskTypes.d.ts +275 -0
  209. package/dist/types/taskTypes.js +36 -0
  210. package/dist/utils/messageBuilder.js +3 -2
  211. package/dist/utils/providerHealth.d.ts +18 -0
  212. package/dist/utils/providerHealth.js +240 -9
  213. package/dist/utils/providerUtils.js +14 -8
  214. package/dist/utils/toolChoice.d.ts +4 -0
  215. package/dist/utils/toolChoice.js +6 -0
  216. package/docs/assets/dashboards/neurolink-proxy-observability-dashboard.json +6609 -0
  217. package/docs/changelog.md +252 -0
  218. package/package.json +19 -1
  219. package/scripts/observability/check-proxy-telemetry.mjs +235 -0
  220. package/scripts/observability/docker-compose.proxy-observability.yaml +55 -0
  221. package/scripts/observability/import-openobserve-dashboard.mjs +240 -0
  222. package/scripts/observability/manage-local-openobserve.sh +184 -0
  223. package/scripts/observability/otel-collector.proxy-observability.yaml +78 -0
  224. package/scripts/observability/proxy-observability.env.example +23 -0
@@ -565,6 +565,7 @@ export class MCPCommandFactory {
565
565
  ? null
566
566
  : ora("Testing MCP server connections...").start();
567
567
  const sdk = new NeuroLink();
568
+ await sdk.getMCPStatus();
568
569
  let serversToTest = await sdk.listMCPServers();
569
570
  if (targetServer) {
570
571
  serversToTest = serversToTest.filter((s) => s.name === targetServer);
@@ -654,6 +655,7 @@ export class MCPCommandFactory {
654
655
  }
655
656
  }
656
657
  const sdk = new NeuroLink();
658
+ await sdk.getMCPStatus();
657
659
  // Check if server exists and is connected
658
660
  const allServers = await sdk.listMCPServers();
659
661
  const server = allServers.find((s) => s.name === serverName);
@@ -762,6 +764,7 @@ export class MCPCommandFactory {
762
764
  process.exit(1);
763
765
  }
764
766
  const sdk = new NeuroLink();
767
+ await sdk.getMCPStatus();
765
768
  const allServers = await sdk.listMCPServers();
766
769
  const server = allServers.find((s) => s.name === serverName);
767
770
  if (!server) {
@@ -10,10 +10,11 @@
10
10
  * (generate/stream), with an optional ModelRouter for model remapping.
11
11
  */
12
12
  import type { CommandModule } from "yargs";
13
- import type { ProxyStartArgs, ProxyStatusArgs, ProxyGuardArgs } from "../../lib/types/index.js";
13
+ import type { ProxyStartArgs, ProxyStatusArgs, ProxyGuardArgs, ProxyTelemetryArgs } from "../../lib/types/index.js";
14
14
  export declare function mapClaudeErrorTypeToStatus(errorType?: string): number;
15
15
  export declare const proxyStartCommand: CommandModule<object, ProxyStartArgs>;
16
16
  export declare const proxyStatusCommand: CommandModule<object, ProxyStatusArgs>;
17
+ export declare const proxyTelemetryCommand: CommandModule<object, ProxyTelemetryArgs>;
17
18
  export declare const proxyGuardCommand: CommandModule<object, ProxyGuardArgs>;
18
19
  export declare const proxySetupCommand: CommandModule;
19
20
  export declare const proxyInstallCommand: CommandModule;
@@ -16,9 +16,12 @@ import chalk from "chalk";
16
16
  import ora from "ora";
17
17
  import { logger } from "../../lib/utils/logger.js";
18
18
  import { formatUptime, isProcessRunning, StateFileManager, } from "../utils/serverUtils.js";
19
+ import { loadProxyEnvFile, resolveProxyEnvFile, } from "../../lib/proxy/proxyEnv.js";
19
20
  import { createRequire } from "node:module";
21
+ import { fileURLToPath } from "node:url";
20
22
  const _require = createRequire(import.meta.url);
21
23
  const { version: PROXY_VERSION } = _require("../../../package.json");
24
+ const PROXY_TELEMETRY_SCRIPT_PATH = fileURLToPath(new URL("../../../scripts/observability/manage-local-openobserve.sh", import.meta.url));
22
25
  // =============================================================================
23
26
  // STATE MANAGEMENT
24
27
  // =============================================================================
@@ -220,6 +223,32 @@ function spawnFailOpenGuard(host, port, parentPid) {
220
223
  return undefined;
221
224
  }
222
225
  }
226
+ async function runProxyTelemetryManager(command) {
227
+ const { existsSync } = await import("fs");
228
+ if (!existsSync(PROXY_TELEMETRY_SCRIPT_PATH)) {
229
+ throw new Error("Proxy telemetry helper files were not found in this installation. Reinstall NeuroLink with observability assets included.");
230
+ }
231
+ await new Promise((resolve, reject) => {
232
+ const child = spawn("bash", [PROXY_TELEMETRY_SCRIPT_PATH, command], {
233
+ stdio: "inherit",
234
+ env: process.env,
235
+ });
236
+ child.on("error", (error) => {
237
+ reject(error);
238
+ });
239
+ child.on("exit", (code, signal) => {
240
+ if (signal) {
241
+ reject(new Error(`proxy telemetry ${command} terminated by signal ${signal}`));
242
+ return;
243
+ }
244
+ if (code !== 0) {
245
+ reject(new Error(`proxy telemetry ${command} exited with code ${code ?? 1}`));
246
+ return;
247
+ }
248
+ resolve();
249
+ });
250
+ });
251
+ }
223
252
  // =============================================================================
224
253
  // STARTUP BANNER
225
254
  // =============================================================================
@@ -313,6 +342,16 @@ export const proxyStartCommand = {
313
342
  alias: "c",
314
343
  description: "Path to proxy config file (YAML/JSON)",
315
344
  defaultDescription: "~/.neurolink/proxy-config.yaml",
345
+ })
346
+ .option("env-file", {
347
+ type: "string",
348
+ alias: "envFile",
349
+ description: "Path to proxy provider env file (overrides cwd .env for the proxy process)",
350
+ })
351
+ .option("passthrough", {
352
+ type: "boolean",
353
+ default: false,
354
+ description: "Run in transparent passthrough mode (no retry, no rotation, no polyfill)",
316
355
  })
317
356
  .example("neurolink proxy start", "Start proxy on default port 55669 with fill-first strategy")
318
357
  .example("neurolink proxy start -p 8080 -s fill-first", "Start proxy on port 8080 with fill-first")
@@ -357,6 +396,22 @@ export const proxyStartCommand = {
357
396
  // -----------------------------------------------------------------
358
397
  // 1. Create NeuroLink instance — reads all env vars automatically
359
398
  // -----------------------------------------------------------------
399
+ let loadedEnvFile;
400
+ try {
401
+ const envResult = await loadProxyEnvFile({
402
+ explicitEnvFile: argv.envFile,
403
+ });
404
+ loadedEnvFile = envResult.path;
405
+ if (spinner && loadedEnvFile) {
406
+ spinner.text = `Loaded proxy env from ${loadedEnvFile}`;
407
+ }
408
+ }
409
+ catch (envError) {
410
+ if (spinner) {
411
+ spinner.fail(chalk.red(envError instanceof Error ? envError.message : String(envError)));
412
+ }
413
+ process.exit(1);
414
+ }
360
415
  // Skip MCP initialization for proxy — tools come from Claude Code, not MCP servers
361
416
  process.env.NEUROLINK_SKIP_MCP = "true";
362
417
  const { NeuroLink } = await import("../../lib/neurolink.js");
@@ -433,15 +488,24 @@ export const proxyStartCommand = {
433
488
  },
434
489
  }, 502);
435
490
  });
436
- const routeGroup = createClaudeProxyRoutes(modelRouter, "", strategy);
491
+ const passthrough = argv.passthrough ?? false;
492
+ const routeGroup = createClaudeProxyRoutes(modelRouter, "", strategy, passthrough);
437
493
  // Register proxy routes — inject NeuroLink into ServerContext
438
494
  for (const route of routeGroup.routes) {
439
495
  const method = route.method.toLowerCase();
440
496
  app[method](route.path, async (c) => {
441
497
  const emptyBody = {};
442
- const body = method === "post"
443
- ? await c.req.json().catch(() => emptyBody)
444
- : undefined;
498
+ let body;
499
+ let rawBody;
500
+ if (method === "post") {
501
+ rawBody = await c.req.text().catch(() => undefined);
502
+ try {
503
+ body = rawBody ? JSON.parse(rawBody) : emptyBody;
504
+ }
505
+ catch {
506
+ body = emptyBody;
507
+ }
508
+ }
445
509
  // Log incoming request
446
510
  const model = body?.model ?? "-";
447
511
  const stream = body?.stream
@@ -461,6 +525,7 @@ export const proxyStartCommand = {
461
525
  query: Object.fromEntries(new URL(c.req.url).searchParams.entries()),
462
526
  params: c.req.param(),
463
527
  body,
528
+ rawBody, // Preserve original bytes for passthrough mode
464
529
  neurolink, // NeuroLink instance for generate/stream
465
530
  toolRegistry: neurolink.getToolRegistry(),
466
531
  timestamp: Date.now(),
@@ -564,6 +629,7 @@ export const proxyStartCommand = {
564
629
  uptime: process.uptime(),
565
630
  version: PROXY_VERSION,
566
631
  stats: {
632
+ totalAttempts: stats.totalAttempts,
567
633
  totalRequests: stats.totalRequests,
568
634
  totalSuccess: stats.totalSuccess,
569
635
  totalErrors: stats.totalErrors,
@@ -571,7 +637,8 @@ export const proxyStartCommand = {
571
637
  accounts: Object.values(stats.accounts).map((a) => ({
572
638
  label: a.label,
573
639
  type: a.type,
574
- requests: a.requestCount,
640
+ attempts: a.attemptCount,
641
+ requests: a.attemptCount,
575
642
  success: a.successCount,
576
643
  errors: a.errorCount,
577
644
  rateLimits: a.rateLimitCount,
@@ -583,7 +650,55 @@ export const proxyStartCommand = {
583
650
  });
584
651
  });
585
652
  // -----------------------------------------------------------------
586
- // 5. Start listening
653
+ // 5. Initialize OpenTelemetry for proxy observability
654
+ // -----------------------------------------------------------------
655
+ try {
656
+ const otlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
657
+ if (!process.env.OTEL_SERVICE_NAME) {
658
+ process.env.OTEL_SERVICE_NAME = "neurolink-proxy";
659
+ }
660
+ // Merge resource attributes (preserving any existing ones)
661
+ process.env.OTEL_RESOURCE_ATTRIBUTES = [
662
+ `service.name=neurolink-proxy`,
663
+ `service.version=${PROXY_VERSION}`,
664
+ `deployment.environment=local`,
665
+ process.env.OTEL_RESOURCE_ATTRIBUTES,
666
+ ]
667
+ .filter(Boolean)
668
+ .join(",");
669
+ const { initializeOpenTelemetry, isOpenTelemetryInitialized } = await import("../../lib/services/server/ai/observability/instrumentation.js");
670
+ const { buildObservabilityConfigFromEnv } = await import("../../lib/utils/observabilityHelpers.js");
671
+ if (!isOpenTelemetryInitialized()) {
672
+ const observabilityConfig = buildObservabilityConfigFromEnv();
673
+ const langfuseConfig = observabilityConfig?.langfuse;
674
+ const langfuseEnabled = langfuseConfig?.enabled === true;
675
+ initializeOpenTelemetry({
676
+ enabled: langfuseEnabled,
677
+ publicKey: langfuseConfig?.publicKey || "",
678
+ secretKey: langfuseConfig?.secretKey || "",
679
+ baseUrl: langfuseConfig?.baseUrl,
680
+ environment: "proxy",
681
+ release: PROXY_VERSION,
682
+ userId: "neurolink-proxy",
683
+ autoDetectOperationName: true,
684
+ });
685
+ if (langfuseEnabled) {
686
+ logger.always(`[proxy] Langfuse enabled — exporting to ${langfuseConfig.baseUrl || "https://cloud.langfuse.com"} (environment=proxy)`);
687
+ }
688
+ if (otlpEndpoint) {
689
+ logger.always(`[proxy] OTLP exporter enabled — exporting to ${otlpEndpoint} (service.name=neurolink-proxy)`);
690
+ }
691
+ if (!langfuseEnabled && !otlpEndpoint) {
692
+ logger.always("[proxy] OpenTelemetry exporters disabled — set OTEL_EXPORTER_OTLP_ENDPOINT or Langfuse credentials to enable proxy observability");
693
+ }
694
+ }
695
+ }
696
+ catch (otelError) {
697
+ // OTel is non-critical — proxy must still work without it
698
+ logger.debug(`[proxy] OpenTelemetry init failed (non-fatal): ${otelError instanceof Error ? otelError.message : String(otelError)}`);
699
+ }
700
+ // -----------------------------------------------------------------
701
+ // 6. Start listening
587
702
  // -----------------------------------------------------------------
588
703
  const port = argv.port ?? 55669;
589
704
  const host = argv.host ?? "127.0.0.1";
@@ -608,9 +723,13 @@ export const proxyStartCommand = {
608
723
  host,
609
724
  strategy,
610
725
  startTime: new Date().toISOString(),
726
+ envFile: loadedEnvFile,
611
727
  fallbackChain,
612
728
  guardPid,
613
- managedBy: "manual",
729
+ managedBy: process.platform === "darwin" && process.ppid === 1
730
+ ? "launchd"
731
+ : "manual",
732
+ passthrough,
614
733
  };
615
734
  saveProxyState(state);
616
735
  if (spinner) {
@@ -619,6 +738,10 @@ export const proxyStartCommand = {
619
738
  const normalizedHost = host === "0.0.0.0" ? "localhost" : host;
620
739
  const url = `http://${normalizedHost}:${port}`;
621
740
  printProxyBanner(url, strategy);
741
+ logger.always(` ${chalk.bold("Mode:")} ${chalk.cyan(passthrough ? "passthrough" : "full")}`);
742
+ if (loadedEnvFile) {
743
+ logger.always(` ${chalk.bold("Env File:")} ${chalk.cyan(loadedEnvFile)}`);
744
+ }
622
745
  // Auto-configure Claude Code — use the normalized URL (localhost, not 0.0.0.0)
623
746
  try {
624
747
  await setClaudeProxySettings(url);
@@ -630,7 +753,7 @@ export const proxyStartCommand = {
630
753
  (e instanceof Error ? e.message : String(e)));
631
754
  }
632
755
  // -----------------------------------------------------------------
633
- // 6. Background token refresh (every 30 seconds)
756
+ // 7. Background token refresh (every 30 seconds)
634
757
  // -----------------------------------------------------------------
635
758
  const { needsRefresh, refreshToken, persistTokens } = await import("../../lib/proxy/tokenRefresh.js");
636
759
  const { tokenStore } = await import("../../lib/auth/tokenStore.js");
@@ -697,12 +820,21 @@ export const proxyStartCommand = {
697
820
  cleanupLogs(7, 500);
698
821
  }, 60 * 60 * 1000);
699
822
  // -----------------------------------------------------------------
700
- // 7. Graceful shutdown
823
+ // 8. Graceful shutdown
701
824
  // -----------------------------------------------------------------
702
825
  const shutdown = async (signal) => {
703
826
  clearInterval(refreshInterval);
704
827
  clearInterval(logCleanupInterval);
705
828
  logger.always(`\nShutting down proxy (${signal})...`);
829
+ // Flush and shutdown OpenTelemetry before closing the server
830
+ try {
831
+ const { flushOpenTelemetry, shutdownOpenTelemetry } = await import("../../lib/services/server/ai/observability/instrumentation.js");
832
+ await flushOpenTelemetry();
833
+ await shutdownOpenTelemetry();
834
+ }
835
+ catch {
836
+ /* non-fatal — proxy shutdown must not block on OTel */
837
+ }
706
838
  // Only clear Claude settings on user-initiated stop (SIGINT).
707
839
  // On SIGTERM (launchd restart cycle), leave settings intact so
708
840
  // the restarted proxy picks up seamlessly.
@@ -746,7 +878,10 @@ export const proxyStartCommand = {
746
878
  };
747
879
  function printStatusStats(stats) {
748
880
  console.info(`\n Stats:`);
749
- console.info(` Requests: ${stats.totalRequests} total, ${stats.totalSuccess} success, ${stats.totalErrors} errors`);
881
+ if (stats.totalAttempts !== undefined) {
882
+ console.info(` Attempts: ${stats.totalAttempts}`);
883
+ }
884
+ console.info(` Completed: ${stats.totalRequests} total, ${stats.totalSuccess} success, ${stats.totalErrors} errors`);
750
885
  console.info(` Rate limits: ${stats.totalRateLimits}`);
751
886
  if (stats.accounts?.length) {
752
887
  console.info(`\n Accounts:`);
@@ -754,7 +889,11 @@ function printStatusStats(stats) {
754
889
  const acctStatus = a.cooling
755
890
  ? chalk.red("cooling")
756
891
  : chalk.green("active");
757
- console.info(` ${a.label.padEnd(20)} ${a.type.padEnd(8)} ${String(a.requests).padEnd(6)} reqs ${acctStatus}`);
892
+ const attempts = a.attempts ?? a.requests ?? 0;
893
+ const success = a.success ?? 0;
894
+ const errors = a.errors ?? 0;
895
+ const rateLimits = a.rateLimits ?? 0;
896
+ console.info(` ${a.label.padEnd(20)} ${a.type.padEnd(8)} ${String(attempts).padEnd(6)} attempts ${String(success).padEnd(6)} success ${String(errors).padEnd(6)} errors ${String(rateLimits).padEnd(6)} rl ${acctStatus}`);
758
897
  }
759
898
  }
760
899
  }
@@ -793,6 +932,7 @@ export const proxyStatusCommand = {
793
932
  uptime: null,
794
933
  startTime: null,
795
934
  url: null,
935
+ envFile: null,
796
936
  fallbackChain: null,
797
937
  };
798
938
  if (state && isProcessRunning(state.pid)) {
@@ -804,10 +944,25 @@ export const proxyStatusCommand = {
804
944
  status.startTime = state.startTime;
805
945
  status.uptime = Date.now() - new Date(state.startTime).getTime();
806
946
  status.url = `http://${state.host === "0.0.0.0" ? "localhost" : state.host}:${state.port}`;
947
+ status.envFile = state.envFile ?? null;
807
948
  status.fallbackChain = state.fallbackChain ?? null;
808
949
  }
950
+ // Fetch live stats before rendering (JSON or text)
951
+ let liveStats = null;
952
+ if (status.running && status.url) {
953
+ try {
954
+ const statusResp = await fetch(`${status.url}/status`);
955
+ if (statusResp.ok) {
956
+ const statusData = (await statusResp.json());
957
+ liveStats = statusData.stats;
958
+ }
959
+ }
960
+ catch {
961
+ // Non-fatal — live stats unavailable
962
+ }
963
+ }
809
964
  if (argv.format === "json") {
810
- logger.always(JSON.stringify(status, null, 2));
965
+ logger.always(JSON.stringify({ ...status, stats: liveStats }, null, 2));
811
966
  return;
812
967
  }
813
968
  // Text format
@@ -822,6 +977,9 @@ export const proxyStatusCommand = {
822
977
  logger.always(` ${chalk.bold("Strategy:")} ${chalk.cyan(status.strategy)}`);
823
978
  logger.always(` ${chalk.bold("Started:")} ${chalk.cyan(status.startTime)}`);
824
979
  logger.always(` ${chalk.bold("Uptime:")} ${chalk.cyan(formatUptime(status.uptime ?? 0))}`);
980
+ if (status.envFile) {
981
+ logger.always(` ${chalk.bold("Env File:")} ${chalk.cyan(status.envFile)}`);
982
+ }
825
983
  // Display fallback chain if configured
826
984
  if (status.fallbackChain && status.fallbackChain.length > 0) {
827
985
  logger.always("");
@@ -875,6 +1033,58 @@ export const proxyStatusCommand = {
875
1033
  },
876
1034
  };
877
1035
  // =============================================================================
1036
+ // PROXY TELEMETRY COMMAND
1037
+ // =============================================================================
1038
+ const PROXY_TELEMETRY_ACTIONS = [
1039
+ "setup",
1040
+ "start",
1041
+ "stop",
1042
+ "status",
1043
+ "logs",
1044
+ "import-dashboard",
1045
+ ];
1046
+ export const proxyTelemetryCommand = {
1047
+ command: "telemetry <action>",
1048
+ describe: "Manage the local OpenObserve stack and dashboard for proxy observability",
1049
+ builder: (yargs) => yargs
1050
+ .positional("action", {
1051
+ type: "string",
1052
+ choices: [...PROXY_TELEMETRY_ACTIONS],
1053
+ describe: "Telemetry action: setup, start, stop, status, logs, or import-dashboard",
1054
+ })
1055
+ .option("quiet", {
1056
+ type: "boolean",
1057
+ alias: "q",
1058
+ default: false,
1059
+ description: "Suppress the local CLI spinner and delegate directly",
1060
+ })
1061
+ .example("neurolink proxy telemetry setup", "Start OpenObserve, start the OTEL collector, and import the dashboard")
1062
+ .example("neurolink proxy telemetry start", "Start the local proxy telemetry stack without re-importing the dashboard")
1063
+ .example("neurolink proxy telemetry stop", "Stop the local OpenObserve and OTEL collector containers"),
1064
+ handler: async (argv) => {
1065
+ const action = argv.action;
1066
+ const spinner = argv.quiet
1067
+ ? null
1068
+ : ora(`Running proxy telemetry ${action}...`).start();
1069
+ try {
1070
+ if (spinner) {
1071
+ spinner.stop();
1072
+ }
1073
+ await runProxyTelemetryManager(action);
1074
+ if (spinner) {
1075
+ spinner.succeed(`proxy telemetry ${action} completed`);
1076
+ }
1077
+ }
1078
+ catch (error) {
1079
+ if (spinner) {
1080
+ spinner.fail(`proxy telemetry ${action} failed`);
1081
+ }
1082
+ logger.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
1083
+ process.exit(1);
1084
+ }
1085
+ },
1086
+ };
1087
+ // =============================================================================
878
1088
  // PROXY FAIL-OPEN GUARD COMMAND (HIDDEN)
879
1089
  // =============================================================================
880
1090
  export const proxyGuardCommand = {
@@ -1152,6 +1362,11 @@ export const proxySetupCommand = {
1152
1362
  type: "boolean",
1153
1363
  default: false,
1154
1364
  description: "Skip service installation and start proxy in foreground instead",
1365
+ })
1366
+ .option("env-file", {
1367
+ type: "string",
1368
+ alias: "envFile",
1369
+ description: "Path to proxy provider env file to persist for the proxy",
1155
1370
  })
1156
1371
  .example("neurolink proxy setup", "Full setup with defaults")
1157
1372
  .example("neurolink proxy setup -p 9000", "Setup on custom port")
@@ -1253,9 +1468,27 @@ export const proxySetupCommand = {
1253
1468
  // =============================================================================
1254
1469
  // PROXY INSTALL / UNINSTALL — launchd service (macOS)
1255
1470
  // =============================================================================
1256
- function buildPlist(port, host) {
1257
- const nodeExec = process.execPath;
1258
- const entryScript = process.argv[1] ?? join(__dirname, "..", "index.js");
1471
+ function escapeXml(s) {
1472
+ return s
1473
+ .replace(/&/g, "&amp;")
1474
+ .replace(/</g, "&lt;")
1475
+ .replace(/>/g, "&gt;")
1476
+ .replace(/"/g, "&quot;")
1477
+ .replace(/'/g, "&apos;");
1478
+ }
1479
+ function buildPlist(port, host, envFile, configFile) {
1480
+ const nodeExec = escapeXml(process.execPath);
1481
+ const entryScript = escapeXml(process.argv[1] ?? join(__dirname, "..", "index.js"));
1482
+ const envFileArgs = envFile
1483
+ ? `
1484
+ <string>--env-file</string>
1485
+ <string>${escapeXml(envFile)}</string>`
1486
+ : "";
1487
+ const configArgs = configFile
1488
+ ? `
1489
+ <string>--config</string>
1490
+ <string>${escapeXml(configFile)}</string>`
1491
+ : "";
1259
1492
  return `<?xml version="1.0" encoding="UTF-8"?>
1260
1493
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
1261
1494
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -1274,6 +1507,8 @@ function buildPlist(port, host) {
1274
1507
  <string>${port}</string>
1275
1508
  <string>--host</string>
1276
1509
  <string>${host}</string>
1510
+ ${envFileArgs}
1511
+ ${configArgs}
1277
1512
  <string>--quiet</string>
1278
1513
  </array>
1279
1514
 
@@ -1320,6 +1555,15 @@ export const proxyInstallCommand = {
1320
1555
  type: "string",
1321
1556
  default: "127.0.0.1",
1322
1557
  description: "Proxy host",
1558
+ })
1559
+ .option("env-file", {
1560
+ type: "string",
1561
+ alias: "envFile",
1562
+ description: "Path to proxy provider env file to persist for the service",
1563
+ })
1564
+ .option("config", {
1565
+ type: "string",
1566
+ description: "Path to proxy routing config file to persist for the service",
1323
1567
  })
1324
1568
  .example("neurolink proxy install", "Install with defaults (port 55669)")
1325
1569
  .example("neurolink proxy install -p 9000", "Install on custom port");
@@ -1333,6 +1577,21 @@ export const proxyInstallCommand = {
1333
1577
  process.exit(1);
1334
1578
  }
1335
1579
  const { writeFileSync, mkdirSync, existsSync } = await import("fs");
1580
+ const envResolution = resolveProxyEnvFile({
1581
+ explicitEnvFile: argv.envFile,
1582
+ });
1583
+ const envFile = envResolution.path;
1584
+ const explicitConfig = argv.config;
1585
+ const configPath = explicitConfig ?? join(homedir(), ".neurolink", "proxy-config.yaml");
1586
+ if (explicitConfig && !existsSync(configPath)) {
1587
+ console.info(chalk.red(`Proxy config file not found: ${configPath}`));
1588
+ process.exit(1);
1589
+ }
1590
+ const configFile = existsSync(configPath) ? configPath : undefined;
1591
+ if (envFile && !existsSync(envFile)) {
1592
+ console.info(chalk.red(`Proxy env file not found: ${envFile}`));
1593
+ process.exit(1);
1594
+ }
1336
1595
  const logsDir = join(homedir(), ".neurolink", "logs");
1337
1596
  if (!existsSync(logsDir)) {
1338
1597
  mkdirSync(logsDir, { recursive: true });
@@ -1340,9 +1599,12 @@ export const proxyInstallCommand = {
1340
1599
  if (!existsSync(PLIST_DIR)) {
1341
1600
  mkdirSync(PLIST_DIR, { recursive: true });
1342
1601
  }
1343
- const plist = buildPlist(port, host);
1602
+ const plist = buildPlist(port, host, envFile, configFile);
1344
1603
  writeFileSync(PLIST_PATH, plist, "utf-8");
1345
1604
  console.info(chalk.green(`✓ Plist written to ${PLIST_PATH}`));
1605
+ if (envFile) {
1606
+ console.info(chalk.green(`✓ Proxy env file: ${envFile}`));
1607
+ }
1346
1608
  try {
1347
1609
  const { execFileSync } = await import("node:child_process");
1348
1610
  execFileSync("launchctl", ["unload", PLIST_PATH], {
@@ -1375,6 +1637,7 @@ export const proxyInstallCommand = {
1375
1637
  host,
1376
1638
  strategy: "fill-first",
1377
1639
  startTime: new Date().toISOString(),
1640
+ envFile,
1378
1641
  managedBy: "launchd",
1379
1642
  });
1380
1643
  }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Task CLI Commands for NeuroLink
3
+ *
4
+ * Implements commands for task scheduling and management:
5
+ * - neurolink task create — Create a scheduled task (pure store write, exits immediately)
6
+ * - neurolink task list — List all tasks
7
+ * - neurolink task get — Show task details
8
+ * - neurolink task run — Run a task immediately
9
+ * - neurolink task pause — Pause a task
10
+ * - neurolink task resume — Resume a paused task
11
+ * - neurolink task update — Update a task
12
+ * - neurolink task delete — Delete a task
13
+ * - neurolink task logs — View run history
14
+ * - neurolink task start — Start worker (keeps process alive for scheduled tasks)
15
+ * - neurolink task stop — Stop a running daemon worker
16
+ * - neurolink task status — Show worker status
17
+ */
18
+ import type { CommandModule } from "yargs";
19
+ export declare class TaskCommandFactory {
20
+ static createTaskCommands(): CommandModule;
21
+ /**
22
+ * Get a full NeuroLink instance (with MCP, tools, providers).
23
+ * Used only by commands that execute AI: run, start/_worker.
24
+ */
25
+ private static getNeuroLink;
26
+ /**
27
+ * Get a direct TaskStore instance for pure store operations.
28
+ * Bypasses NeuroLink entirely — no MCP, no providers, no tools.
29
+ * Respects the same backend selection as TaskManager so both paths
30
+ * read/write the same store (Redis for bullmq, file for node-timeout).
31
+ *
32
+ * Used by all management commands: create, list, get, delete, logs, pause, resume, update.
33
+ */
34
+ private static getStore;
35
+ /** Attach event listeners and keep the process alive for scheduled task execution */
36
+ private static enterWorkerMode;
37
+ /**
38
+ * Create — pure store write, no NeuroLink needed.
39
+ * Builds the Task object directly, saves to the task store, exits immediately.
40
+ */
41
+ private static executeCreate;
42
+ private static executeList;
43
+ private static executeGet;
44
+ private static executeRun;
45
+ private static executePause;
46
+ private static executeResume;
47
+ private static executeUpdate;
48
+ private static executeDelete;
49
+ private static executeLogs;
50
+ private static executeStart;
51
+ private static executeStop;
52
+ private static executeStatus;
53
+ private static spawnDaemon;
54
+ private static runForegroundWorker;
55
+ private static executeWorkerProcess;
56
+ }