@juspay/neurolink 9.33.0 → 9.35.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.
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Proxy Quiet Detector
3
+ * Determines whether the proxy has been idle (no traffic) for a given
4
+ * threshold by efficiently reading only the tail of today's debug log file.
5
+ * Used by the auto-update system to find safe windows for restarts.
6
+ */
7
+ /** Result of a traffic-quiet check. */
8
+ export interface QuietStatus {
9
+ isQuiet: boolean;
10
+ lastActivityAt: Date | null;
11
+ silenceDurationMs: number;
12
+ }
13
+ /**
14
+ * Check whether proxy traffic has been quiet (no requests) for at least
15
+ * `quietThresholdMs` milliseconds.
16
+ *
17
+ * Reads only the tail of today's debug log file for efficiency.
18
+ *
19
+ * @param quietThresholdMs Silence duration (ms) to consider "quiet". Default: 120 000 (2 min).
20
+ * @returns QuietStatus with the idle analysis.
21
+ */
22
+ export declare function checkTrafficQuiet(quietThresholdMs?: number): QuietStatus;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Proxy Quiet Detector
3
+ * Determines whether the proxy has been idle (no traffic) for a given
4
+ * threshold by efficiently reading only the tail of today's debug log file.
5
+ * Used by the auto-update system to find safe windows for restarts.
6
+ */
7
+ import { openSync, readSync, closeSync, fstatSync, existsSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { homedir } from "node:os";
10
+ /** Default quiet threshold: 2 minutes of no traffic. */
11
+ const DEFAULT_QUIET_THRESHOLD_MS = 120_000;
12
+ /** Maximum bytes to read from the tail of the log file. */
13
+ const TAIL_READ_SIZE = 4096;
14
+ /**
15
+ * Build the path to today's proxy debug log file.
16
+ * Format: ~/.neurolink/logs/proxy-debug-YYYY-MM-DD.jsonl
17
+ */
18
+ function getTodayLogPath() {
19
+ const today = new Date().toISOString().split("T")[0];
20
+ return join(homedir(), ".neurolink", "logs", `proxy-debug-${today}.jsonl`);
21
+ }
22
+ /**
23
+ * Read the last complete line(s) from a file efficiently.
24
+ * Uses low-level fs to seek to end and read only the last TAIL_READ_SIZE bytes.
25
+ * Returns an array of the last non-empty lines (up to 2 for fallback).
26
+ */
27
+ function readTailLines(filePath) {
28
+ let fd = null;
29
+ try {
30
+ fd = openSync(filePath, "r");
31
+ const stat = fstatSync(fd);
32
+ if (stat.size === 0) {
33
+ return [];
34
+ }
35
+ const readSize = Math.min(TAIL_READ_SIZE, stat.size);
36
+ const offset = stat.size - readSize;
37
+ const buffer = Buffer.alloc(readSize);
38
+ readSync(fd, buffer, 0, readSize, offset);
39
+ const chunk = buffer.toString("utf-8");
40
+ // Split into lines, filter out empty trailing entries
41
+ const lines = chunk.split("\n").filter((line) => line.trim().length > 0);
42
+ // Return last 2 lines (last + fallback)
43
+ return lines.slice(-2);
44
+ }
45
+ finally {
46
+ if (fd !== null) {
47
+ closeSync(fd);
48
+ }
49
+ }
50
+ }
51
+ /**
52
+ * Try to parse a JSON line and extract its ISO timestamp.
53
+ * Returns the timestamp as epoch ms, or null if parsing fails.
54
+ */
55
+ function extractTimestamp(line) {
56
+ try {
57
+ const parsed = JSON.parse(line);
58
+ if (typeof parsed.timestamp === "string") {
59
+ const ms = Date.parse(parsed.timestamp);
60
+ if (!Number.isNaN(ms)) {
61
+ return ms;
62
+ }
63
+ }
64
+ }
65
+ catch {
66
+ // Malformed JSON — caller will handle fallback
67
+ }
68
+ return null;
69
+ }
70
+ /**
71
+ * Check whether proxy traffic has been quiet (no requests) for at least
72
+ * `quietThresholdMs` milliseconds.
73
+ *
74
+ * Reads only the tail of today's debug log file for efficiency.
75
+ *
76
+ * @param quietThresholdMs Silence duration (ms) to consider "quiet". Default: 120 000 (2 min).
77
+ * @returns QuietStatus with the idle analysis.
78
+ */
79
+ export function checkTrafficQuiet(quietThresholdMs = DEFAULT_QUIET_THRESHOLD_MS) {
80
+ const noActivityResult = {
81
+ isQuiet: true,
82
+ lastActivityAt: null,
83
+ silenceDurationMs: Infinity,
84
+ };
85
+ const logPath = getTodayLogPath();
86
+ if (!existsSync(logPath)) {
87
+ return noActivityResult;
88
+ }
89
+ const tailLines = readTailLines(logPath);
90
+ if (tailLines.length === 0) {
91
+ return noActivityResult;
92
+ }
93
+ // Try last line first, then fall back to the one before it
94
+ let timestampMs = null;
95
+ for (let i = tailLines.length - 1; i >= 0; i--) {
96
+ timestampMs = extractTimestamp(tailLines[i]);
97
+ if (timestampMs !== null) {
98
+ break;
99
+ }
100
+ }
101
+ if (timestampMs === null) {
102
+ // All tail lines are malformed — treat as quiet
103
+ return noActivityResult;
104
+ }
105
+ const silenceDurationMs = Date.now() - timestampMs;
106
+ return {
107
+ isQuiet: silenceDurationMs >= quietThresholdMs,
108
+ lastActivityAt: new Date(timestampMs),
109
+ silenceDurationMs,
110
+ };
111
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Proxy auto-update version checker.
3
+ *
4
+ * Queries the npm registry for the latest published version of
5
+ * `@juspay/neurolink` and compares it against the currently running version.
6
+ * Designed to be non-blocking and failure-tolerant — any error (network,
7
+ * timeout, parse) silently returns `updateAvailable: false`.
8
+ */
9
+ export interface UpdateCheckResult {
10
+ currentVersion: string;
11
+ latestVersion: string;
12
+ updateAvailable: boolean;
13
+ }
14
+ /**
15
+ * Query npm for the latest version of `@juspay/neurolink` and compare it
16
+ * against {@link currentVersion}.
17
+ *
18
+ * On **any** failure the function resolves (never rejects) with
19
+ * `{ updateAvailable: false, latestVersion: currentVersion }`.
20
+ */
21
+ export declare function checkForUpdate(currentVersion: string): Promise<UpdateCheckResult>;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Proxy auto-update version checker.
3
+ *
4
+ * Queries the npm registry for the latest published version of
5
+ * `@juspay/neurolink` and compares it against the currently running version.
6
+ * Designed to be non-blocking and failure-tolerant — any error (network,
7
+ * timeout, parse) silently returns `updateAvailable: false`.
8
+ */
9
+ import { execFile as execFileCb } from "node:child_process";
10
+ import { promisify } from "node:util";
11
+ import { logger } from "../utils/logger.js";
12
+ const execFile = promisify(execFileCb);
13
+ /** Timeout (ms) for the `npm view` child process. */
14
+ const NPM_VIEW_TIMEOUT_MS = 10_000;
15
+ /**
16
+ * Parse a version string of the form `major.minor.patch` into numeric
17
+ * components. Returns `null` when the string does not match.
18
+ */
19
+ function parseSemVer(version) {
20
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version.trim());
21
+ if (!match) {
22
+ return null;
23
+ }
24
+ return {
25
+ major: Number(match[1]),
26
+ minor: Number(match[2]),
27
+ patch: Number(match[3]),
28
+ };
29
+ }
30
+ /**
31
+ * Returns `true` when `latest` is strictly greater than `current`.
32
+ *
33
+ * Both arguments must be valid semver strings; returns `false` on any
34
+ * parse failure so the caller never sees a spurious "update available".
35
+ */
36
+ function isNewerVersion(current, latest) {
37
+ const cur = parseSemVer(current);
38
+ const lat = parseSemVer(latest);
39
+ if (!cur || !lat) {
40
+ return false;
41
+ }
42
+ if (lat.major !== cur.major) {
43
+ return lat.major > cur.major;
44
+ }
45
+ if (lat.minor !== cur.minor) {
46
+ return lat.minor > cur.minor;
47
+ }
48
+ return lat.patch > cur.patch;
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // Core check
52
+ // ---------------------------------------------------------------------------
53
+ /**
54
+ * Query npm for the latest version of `@juspay/neurolink` and compare it
55
+ * against {@link currentVersion}.
56
+ *
57
+ * On **any** failure the function resolves (never rejects) with
58
+ * `{ updateAvailable: false, latestVersion: currentVersion }`.
59
+ */
60
+ export async function checkForUpdate(currentVersion) {
61
+ const fail = {
62
+ currentVersion,
63
+ latestVersion: currentVersion,
64
+ updateAvailable: false,
65
+ };
66
+ try {
67
+ logger.debug("[UpdateChecker] Checking for updates", { currentVersion });
68
+ const { stdout } = await execFile("npm", ["view", "@juspay/neurolink", "version", "--json"], { timeout: NPM_VIEW_TIMEOUT_MS });
69
+ // `npm view ... --json` wraps the value in double-quotes, e.g. `"9.32.0"`
70
+ const parsed = JSON.parse(stdout);
71
+ if (typeof parsed !== "string") {
72
+ logger.warn("[UpdateChecker] Unexpected npm output type", {
73
+ type: typeof parsed,
74
+ });
75
+ return fail;
76
+ }
77
+ const latestVersion = parsed.trim();
78
+ if (!parseSemVer(latestVersion)) {
79
+ logger.warn("[UpdateChecker] Failed to parse latest version", {
80
+ latestVersion,
81
+ });
82
+ return fail;
83
+ }
84
+ const updateAvailable = isNewerVersion(currentVersion, latestVersion);
85
+ logger.debug("[UpdateChecker] Version check complete", {
86
+ currentVersion,
87
+ latestVersion,
88
+ updateAvailable,
89
+ });
90
+ return { currentVersion, latestVersion, updateAvailable };
91
+ }
92
+ catch (error) {
93
+ const message = error instanceof Error ? error.message : String(error);
94
+ logger.warn("[UpdateChecker] Update check failed", { error: message });
95
+ return fail;
96
+ }
97
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Update State Persistence
3
+ * Manages persistent state for the proxy auto-update feature.
4
+ * Tracks check timestamps, suppressed versions, and update history.
5
+ *
6
+ * State file location: ~/.neurolink/update-state.json
7
+ * Suppressed versions expire after 24 hours.
8
+ */
9
+ export interface SuppressedVersion {
10
+ suppressedAt: string;
11
+ reason: string;
12
+ }
13
+ export interface UpdateState {
14
+ lastCheckAt: string;
15
+ lastCheckVersion: string;
16
+ suppressedVersions: Record<string, SuppressedVersion>;
17
+ lastUpdateAt: string | null;
18
+ lastUpdateVersion: string | null;
19
+ }
20
+ /**
21
+ * Return an empty/initial UpdateState.
22
+ */
23
+ export declare function getDefaultUpdateState(): UpdateState;
24
+ /**
25
+ * Load the update state from disk.
26
+ * Returns null if the file does not exist.
27
+ * Returns the default state if the file contains corrupt JSON.
28
+ *
29
+ * @param stateFilePath - Override path for testing (default: ~/.neurolink/update-state.json)
30
+ */
31
+ export declare function loadUpdateState(stateFilePath?: string): UpdateState | null;
32
+ /**
33
+ * Save the update state to disk.
34
+ *
35
+ * @param state - The UpdateState to persist
36
+ * @param stateFilePath - Override path for testing (default: ~/.neurolink/update-state.json)
37
+ */
38
+ export declare function saveUpdateState(state: UpdateState, stateFilePath?: string): void;
39
+ /**
40
+ * Check whether a version is currently suppressed (i.e., suppressed AND within the 24-hour window).
41
+ *
42
+ * @param version - Semver version string to check
43
+ * @param stateFilePath - Override path for testing
44
+ */
45
+ export declare function isVersionSuppressed(version: string, stateFilePath?: string): boolean;
46
+ /**
47
+ * Add a version to the suppressed list and persist.
48
+ *
49
+ * @param version - Semver version string to suppress
50
+ * @param reason - Human-readable reason for suppression
51
+ * @param stateFilePath - Override path for testing
52
+ */
53
+ export declare function suppressVersion(version: string, reason: string, stateFilePath?: string): void;
54
+ /**
55
+ * Record a successful update: set lastUpdateAt and lastUpdateVersion, then persist.
56
+ *
57
+ * @param version - The version that was successfully installed
58
+ * @param stateFilePath - Override path for testing
59
+ */
60
+ export declare function recordSuccessfulUpdate(version: string, stateFilePath?: string): void;
61
+ /**
62
+ * Record an update check: set lastCheckAt and lastCheckVersion, then persist.
63
+ *
64
+ * @param latestVersion - The latest version found during the check
65
+ * @param stateFilePath - Override path for testing
66
+ */
67
+ export declare function recordCheck(latestVersion: string, stateFilePath?: string): void;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Update State Persistence
3
+ * Manages persistent state for the proxy auto-update feature.
4
+ * Tracks check timestamps, suppressed versions, and update history.
5
+ *
6
+ * State file location: ~/.neurolink/update-state.json
7
+ * Suppressed versions expire after 24 hours.
8
+ */
9
+ import fs from "fs";
10
+ import os from "os";
11
+ import path from "path";
12
+ // ============================================
13
+ // Constants
14
+ // ============================================
15
+ const STATE_FILENAME = "update-state.json";
16
+ const SUPPRESSION_TTL_MS = 86_400_000; // 24 hours
17
+ // ============================================
18
+ // Internal Helpers
19
+ // ============================================
20
+ /**
21
+ * Resolve the path to the update state file.
22
+ * Accepts an override for testing; defaults to ~/.neurolink/update-state.json.
23
+ */
24
+ function resolveStatePath(overridePath) {
25
+ if (overridePath) {
26
+ return overridePath;
27
+ }
28
+ return path.join(os.homedir(), ".neurolink", STATE_FILENAME);
29
+ }
30
+ /**
31
+ * Ensure the parent directory of the given file path exists.
32
+ */
33
+ function ensureParentDir(filePath) {
34
+ const dir = path.dirname(filePath);
35
+ if (!fs.existsSync(dir)) {
36
+ fs.mkdirSync(dir, { recursive: true });
37
+ }
38
+ }
39
+ // ============================================
40
+ // Exported Functions
41
+ // ============================================
42
+ /**
43
+ * Return an empty/initial UpdateState.
44
+ */
45
+ export function getDefaultUpdateState() {
46
+ return {
47
+ lastCheckAt: new Date(0).toISOString(),
48
+ lastCheckVersion: "",
49
+ suppressedVersions: {},
50
+ lastUpdateAt: null,
51
+ lastUpdateVersion: null,
52
+ };
53
+ }
54
+ /**
55
+ * Load the update state from disk.
56
+ * Returns null if the file does not exist.
57
+ * Returns the default state if the file contains corrupt JSON.
58
+ *
59
+ * @param stateFilePath - Override path for testing (default: ~/.neurolink/update-state.json)
60
+ */
61
+ export function loadUpdateState(stateFilePath) {
62
+ const filePath = resolveStatePath(stateFilePath);
63
+ try {
64
+ if (!fs.existsSync(filePath)) {
65
+ return null;
66
+ }
67
+ const content = fs.readFileSync(filePath, "utf8");
68
+ const parsed = JSON.parse(content);
69
+ // Minimal shape check — reject valid JSON that isn't an UpdateState
70
+ if (typeof parsed !== "object" ||
71
+ parsed === null ||
72
+ typeof parsed.suppressedVersions !== "object" ||
73
+ typeof parsed.lastCheckAt !== "string") {
74
+ return getDefaultUpdateState();
75
+ }
76
+ return parsed;
77
+ }
78
+ catch {
79
+ // Corrupt or unreadable JSON — return default state
80
+ return getDefaultUpdateState();
81
+ }
82
+ }
83
+ /**
84
+ * Save the update state to disk.
85
+ *
86
+ * @param state - The UpdateState to persist
87
+ * @param stateFilePath - Override path for testing (default: ~/.neurolink/update-state.json)
88
+ */
89
+ export function saveUpdateState(state, stateFilePath) {
90
+ const filePath = resolveStatePath(stateFilePath);
91
+ ensureParentDir(filePath);
92
+ // Atomic write: write to temp file then rename to prevent corruption on crash
93
+ const tmpPath = filePath + ".tmp";
94
+ fs.writeFileSync(tmpPath, JSON.stringify(state, null, 2));
95
+ fs.renameSync(tmpPath, filePath);
96
+ }
97
+ /**
98
+ * Check whether a version is currently suppressed (i.e., suppressed AND within the 24-hour window).
99
+ *
100
+ * @param version - Semver version string to check
101
+ * @param stateFilePath - Override path for testing
102
+ */
103
+ export function isVersionSuppressed(version, stateFilePath) {
104
+ const state = loadUpdateState(stateFilePath);
105
+ if (!state) {
106
+ return false;
107
+ }
108
+ const entry = state.suppressedVersions[version];
109
+ if (!entry) {
110
+ return false;
111
+ }
112
+ return Date.now() - Date.parse(entry.suppressedAt) < SUPPRESSION_TTL_MS;
113
+ }
114
+ /**
115
+ * Add a version to the suppressed list and persist.
116
+ *
117
+ * @param version - Semver version string to suppress
118
+ * @param reason - Human-readable reason for suppression
119
+ * @param stateFilePath - Override path for testing
120
+ */
121
+ export function suppressVersion(version, reason, stateFilePath) {
122
+ const state = loadUpdateState(stateFilePath) ?? getDefaultUpdateState();
123
+ state.suppressedVersions[version] = {
124
+ suppressedAt: new Date().toISOString(),
125
+ reason,
126
+ };
127
+ saveUpdateState(state, stateFilePath);
128
+ }
129
+ /**
130
+ * Record a successful update: set lastUpdateAt and lastUpdateVersion, then persist.
131
+ *
132
+ * @param version - The version that was successfully installed
133
+ * @param stateFilePath - Override path for testing
134
+ */
135
+ export function recordSuccessfulUpdate(version, stateFilePath) {
136
+ const state = loadUpdateState(stateFilePath) ?? getDefaultUpdateState();
137
+ state.lastUpdateAt = new Date().toISOString();
138
+ state.lastUpdateVersion = version;
139
+ saveUpdateState(state, stateFilePath);
140
+ }
141
+ /**
142
+ * Record an update check: set lastCheckAt and lastCheckVersion, then persist.
143
+ *
144
+ * @param latestVersion - The latest version found during the check
145
+ * @param stateFilePath - Override path for testing
146
+ */
147
+ export function recordCheck(latestVersion, stateFilePath) {
148
+ const state = loadUpdateState(stateFilePath) ?? getDefaultUpdateState();
149
+ state.lastCheckAt = new Date().toISOString();
150
+ state.lastCheckVersion = latestVersion;
151
+ saveUpdateState(state, stateFilePath);
152
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "9.33.0",
3
+ "version": "9.35.0",
4
4
  "description": "Universal AI Development Platform with working MCP integration, multi-provider support, and professional CLI. Built-in tools operational, 58+ external MCP servers discoverable. Connect to filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 13 providers: OpenAI, Anthropic, Google AI, AWS Bedrock, Azure, Hugging Face, Ollama, and Mistral AI.",
5
5
  "author": {
6
6
  "name": "Juspay Technologies",
@@ -28,13 +28,15 @@
28
28
  "scripts": {
29
29
  "dev": "vite dev",
30
30
  "build": "vite build && pnpm run prepack",
31
+ "build:browser": "node scripts/build-browser.mjs",
32
+ "build:browser:dev": "node scripts/build-browser.mjs --dev",
31
33
  "build:cli": "echo 'Building CLI...' && svelte-kit sync && tsc --project tsconfig.cli.json",
32
34
  "build:action": "ncc build src/action/index.ts -o action-dist --source-map",
33
35
  "build:cli:link": "pnpm run build:cli && pnpm link --global",
34
36
  "cli": "node dist/cli/index.js",
35
37
  "preview": "vite preview",
36
38
  "prepare": "git rev-parse --git-dir > /dev/null 2>&1 && husky install || echo 'Skipping husky in non-git environment'",
37
- "prepack": "svelte-kit sync && svelte-package && pnpm run build:react-hooks && pnpm run build:cli && publint",
39
+ "prepack": "svelte-kit sync && svelte-package && pnpm run build:react-hooks && pnpm run build:cli && pnpm run build:browser && publint",
38
40
  "build:react-hooks": "npx tsc --jsx react-jsx --module nodenext --moduleResolution nodenext --target esnext --esModuleInterop --skipLibCheck --outDir dist --declaration false src/lib/client/reactHooks.tsx",
39
41
  "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json && tsc --noEmit --strict",
40
42
  "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
@@ -172,6 +174,11 @@
172
174
  "types": "./dist/server/index.d.ts",
173
175
  "import": "./dist/server/index.js",
174
176
  "default": "./dist/server/index.js"
177
+ },
178
+ "./browser": {
179
+ "types": "./dist/index.d.ts",
180
+ "import": "./dist/browser/neurolink.min.js",
181
+ "default": "./dist/browser/neurolink.min.js"
175
182
  }
176
183
  },
177
184
  "dependencies": {
@@ -309,6 +316,7 @@
309
316
  "@vitest/coverage-v8": "^4.1.0",
310
317
  "concurrently": "^9.2.1",
311
318
  "conventional-changelog-conventionalcommits": "^9.1.0",
319
+ "esbuild": "^0.27.4",
312
320
  "eslint": "^10.0.2",
313
321
  "husky": "^9.1.7",
314
322
  "lint-staged": "^16.3.0",