@juspay/neurolink 9.50.2 → 9.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/browser/neurolink.min.js +249 -249
- package/dist/cli/commands/proxy.js +60 -15
- package/dist/cli/utils/serverUtils.d.ts +2 -1
- package/dist/cli/utils/serverUtils.js +7 -3
- package/dist/lib/proxy/accountQuota.d.ts +6 -0
- package/dist/lib/proxy/accountQuota.js +24 -3
- package/dist/lib/proxy/proxyPaths.d.ts +25 -0
- package/dist/lib/proxy/proxyPaths.js +35 -0
- package/dist/lib/proxy/requestLogger.d.ts +1 -1
- package/dist/lib/proxy/requestLogger.js +2 -2
- package/dist/lib/types/cli.d.ts +1 -0
- package/dist/proxy/accountQuota.d.ts +6 -0
- package/dist/proxy/accountQuota.js +24 -3
- package/dist/proxy/proxyPaths.d.ts +25 -0
- package/dist/proxy/proxyPaths.js +34 -0
- package/dist/proxy/requestLogger.d.ts +1 -1
- package/dist/proxy/requestLogger.js +2 -2
- package/dist/types/cli.d.ts +1 -0
- package/package.json +1 -1
|
@@ -26,7 +26,14 @@ const PROXY_TELEMETRY_SCRIPT_PATH = fileURLToPath(new URL("../../../scripts/obse
|
|
|
26
26
|
// =============================================================================
|
|
27
27
|
// STATE MANAGEMENT
|
|
28
28
|
// =============================================================================
|
|
29
|
-
|
|
29
|
+
let proxyStateManager = new StateFileManager("proxy-state.json");
|
|
30
|
+
/**
|
|
31
|
+
* Reinitialise the state manager with a custom base directory.
|
|
32
|
+
* Called when --dev redirects writable paths to .neurolink-dev/.
|
|
33
|
+
*/
|
|
34
|
+
function setProxyStateDir(baseDir) {
|
|
35
|
+
proxyStateManager = new StateFileManager("proxy-state.json", baseDir);
|
|
36
|
+
}
|
|
30
37
|
function saveProxyState(state) {
|
|
31
38
|
proxyStateManager.save(state);
|
|
32
39
|
}
|
|
@@ -333,12 +340,12 @@ async function loadProxyStartEnv(argv, spinner) {
|
|
|
333
340
|
process.exit(1);
|
|
334
341
|
}
|
|
335
342
|
}
|
|
336
|
-
async function createProxyNeurolinkRuntime() {
|
|
343
|
+
async function createProxyNeurolinkRuntime(logsDir) {
|
|
337
344
|
process.env.NEUROLINK_SKIP_MCP = "true";
|
|
338
345
|
const { NeuroLink } = await import("../../lib/neurolink.js");
|
|
339
346
|
const neurolink = new NeuroLink();
|
|
340
347
|
const { initRequestLogger, cleanupLogs } = await import("../../lib/proxy/requestLogger.js");
|
|
341
|
-
initRequestLogger(true);
|
|
348
|
+
initRequestLogger(true, logsDir);
|
|
342
349
|
cleanupLogs(7, 500);
|
|
343
350
|
return { neurolink, cleanupLogs };
|
|
344
351
|
}
|
|
@@ -701,7 +708,7 @@ function registerProxyShutdownHandlers(params) {
|
|
|
701
708
|
catch {
|
|
702
709
|
// non-fatal — proxy shutdown must not block on OTel
|
|
703
710
|
}
|
|
704
|
-
if (signal === "SIGINT") {
|
|
711
|
+
if (signal === "SIGINT" && !params.isDev) {
|
|
705
712
|
try {
|
|
706
713
|
const shutdownHost = params.host === "0.0.0.0" ? "localhost" : params.host;
|
|
707
714
|
await clearClaudeProxySettings(`http://${shutdownHost}:${params.port}`);
|
|
@@ -733,7 +740,11 @@ async function startProxyRuntime(params) {
|
|
|
733
740
|
port: params.port,
|
|
734
741
|
hostname: params.host,
|
|
735
742
|
});
|
|
736
|
-
|
|
743
|
+
// Skip the fail-open guard in dev mode — it monitors the proxy and clears
|
|
744
|
+
// global Claude settings on exit, which is exactly what we want to avoid.
|
|
745
|
+
const guardPid = params.argv.dev
|
|
746
|
+
? undefined
|
|
747
|
+
: spawnFailOpenGuard(params.host, params.port, process.pid);
|
|
737
748
|
const readinessHost = params.host === "0.0.0.0" ? "127.0.0.1" : params.host;
|
|
738
749
|
await waitForProxyReadiness({
|
|
739
750
|
host: readinessHost,
|
|
@@ -767,10 +778,16 @@ async function startProxyRuntime(params) {
|
|
|
767
778
|
if (params.spinner) {
|
|
768
779
|
params.spinner.succeed(chalk.green("Claude proxy started successfully"));
|
|
769
780
|
}
|
|
781
|
+
const isDev = params.argv.dev ?? false;
|
|
770
782
|
const normalizedHost = params.host === "0.0.0.0" ? "localhost" : params.host;
|
|
771
783
|
const url = `http://${normalizedHost}:${params.port}`;
|
|
772
784
|
printProxyBanner(url, params.strategy);
|
|
773
|
-
|
|
785
|
+
if (isDev) {
|
|
786
|
+
logger.always(` ${chalk.bold("Mode:")} ${chalk.magenta("dev (isolated — state in .neurolink-dev/)")}`);
|
|
787
|
+
}
|
|
788
|
+
else {
|
|
789
|
+
logger.always(` ${chalk.bold("Mode:")} ${chalk.cyan(params.passthrough ? "passthrough" : "full")}`);
|
|
790
|
+
}
|
|
774
791
|
if (params.passthrough) {
|
|
775
792
|
logger.always(chalk.yellow(" ! Passthrough mode forwards client auth directly to Anthropic"));
|
|
776
793
|
logger.always(chalk.dim(" Stored proxy OAuth/API credentials are ignored; clients need their own valid Anthropic auth."));
|
|
@@ -778,29 +795,52 @@ async function startProxyRuntime(params) {
|
|
|
778
795
|
if (params.loadedEnvFile) {
|
|
779
796
|
logger.always(` ${chalk.bold("Env File:")} ${chalk.cyan(params.loadedEnvFile)}`);
|
|
780
797
|
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
798
|
+
if (!isDev) {
|
|
799
|
+
try {
|
|
800
|
+
await setClaudeProxySettings(url);
|
|
801
|
+
logger.always(chalk.green(" ✓ Auto-configured Claude Code settings"));
|
|
802
|
+
logger.always(chalk.dim(" Restart Claude Code to connect through proxy"));
|
|
803
|
+
}
|
|
804
|
+
catch (error) {
|
|
805
|
+
logger.debug("[proxy] Failed to auto-configure Claude Code: " +
|
|
806
|
+
(error instanceof Error ? error.message : String(error)));
|
|
807
|
+
}
|
|
785
808
|
}
|
|
786
|
-
|
|
787
|
-
logger.
|
|
788
|
-
(error instanceof Error ? error.message : String(error)));
|
|
809
|
+
else {
|
|
810
|
+
logger.always(chalk.dim(" ⊘ Dev mode: skipping client auto-configuration"));
|
|
789
811
|
}
|
|
790
812
|
const maintenance = startProxyBackgroundMaintenance(params.cleanupLogs);
|
|
791
813
|
registerProxyShutdownHandlers({
|
|
792
814
|
server,
|
|
793
815
|
host: params.host,
|
|
794
816
|
port: params.port,
|
|
817
|
+
isDev,
|
|
795
818
|
...maintenance,
|
|
796
819
|
});
|
|
797
820
|
}
|
|
798
821
|
async function startProxyCommandHandler(argv) {
|
|
799
822
|
const spinner = argv.quiet ? null : ora("Starting Claude proxy...").start();
|
|
823
|
+
const isDev = argv.dev ?? false;
|
|
800
824
|
try {
|
|
801
|
-
|
|
825
|
+
// In dev mode: redirect writable state to .neurolink-dev/ and skip singleton check
|
|
826
|
+
let devPaths;
|
|
827
|
+
if (isDev) {
|
|
828
|
+
const { resolveProxyPaths } = await import("../../lib/proxy/proxyPaths.js");
|
|
829
|
+
devPaths = resolveProxyPaths(true);
|
|
830
|
+
setProxyStateDir(devPaths.stateDir);
|
|
831
|
+
const { initAccountQuota } = await import("../../lib/proxy/accountQuota.js");
|
|
832
|
+
initAccountQuota(devPaths.quotaFile);
|
|
833
|
+
// Ensure the dev state directory exists
|
|
834
|
+
const { mkdirSync, existsSync } = await import("fs");
|
|
835
|
+
if (!existsSync(devPaths.stateDir)) {
|
|
836
|
+
mkdirSync(devPaths.stateDir, { recursive: true, mode: 0o700 });
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (!isDev) {
|
|
840
|
+
await ensureProxyStartAllowed(spinner);
|
|
841
|
+
}
|
|
802
842
|
const loadedEnvFile = await loadProxyStartEnv(argv, spinner);
|
|
803
|
-
const { neurolink, cleanupLogs } = await createProxyNeurolinkRuntime();
|
|
843
|
+
const { neurolink, cleanupLogs } = await createProxyNeurolinkRuntime(devPaths?.logsDir);
|
|
804
844
|
const { proxyConfig, strategy, modelRouter, passthrough } = await loadProxyStartConfiguration(argv, spinner);
|
|
805
845
|
if (spinner) {
|
|
806
846
|
spinner.text = "Configuring server...";
|
|
@@ -904,6 +944,11 @@ export const proxyStartCommand = {
|
|
|
904
944
|
type: "boolean",
|
|
905
945
|
default: false,
|
|
906
946
|
description: "Run in transparent passthrough mode (no retry, no rotation, no polyfill)",
|
|
947
|
+
})
|
|
948
|
+
.option("dev", {
|
|
949
|
+
type: "boolean",
|
|
950
|
+
default: false,
|
|
951
|
+
description: "Run in isolated dev mode — state files scoped to .neurolink-dev/ in cwd, no client auto-configuration, no singleton check",
|
|
907
952
|
})
|
|
908
953
|
.example("neurolink proxy start", "Start proxy on default port 55669 with fill-first strategy")
|
|
909
954
|
.example("neurolink proxy start -p 8080 -s fill-first", "Start proxy on port 8080 with fill-first")
|
|
@@ -44,8 +44,9 @@ export declare class StateFileManager<T> {
|
|
|
44
44
|
/**
|
|
45
45
|
* Create a new state file manager
|
|
46
46
|
* @param filename - Name of the state file (e.g., "serve-state.json")
|
|
47
|
+
* @param baseDir - Optional base directory (defaults to ~/.neurolink)
|
|
47
48
|
*/
|
|
48
|
-
constructor(filename: string);
|
|
49
|
+
constructor(filename: string, baseDir?: string);
|
|
49
50
|
/**
|
|
50
51
|
* Get the full path to the state file
|
|
51
52
|
*/
|
|
@@ -92,9 +92,10 @@ export class StateFileManager {
|
|
|
92
92
|
/**
|
|
93
93
|
* Create a new state file manager
|
|
94
94
|
* @param filename - Name of the state file (e.g., "serve-state.json")
|
|
95
|
+
* @param baseDir - Optional base directory (defaults to ~/.neurolink)
|
|
95
96
|
*/
|
|
96
|
-
constructor(filename) {
|
|
97
|
-
this.filePath = path.join(getNeuroLinkDir(), filename);
|
|
97
|
+
constructor(filename, baseDir) {
|
|
98
|
+
this.filePath = path.join(baseDir ?? getNeuroLinkDir(), filename);
|
|
98
99
|
}
|
|
99
100
|
/**
|
|
100
101
|
* Get the full path to the state file
|
|
@@ -107,7 +108,10 @@ export class StateFileManager {
|
|
|
107
108
|
* @param state - State object to save
|
|
108
109
|
*/
|
|
109
110
|
save(state) {
|
|
110
|
-
|
|
111
|
+
const dir = path.dirname(this.filePath);
|
|
112
|
+
if (!fs.existsSync(dir)) {
|
|
113
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
114
|
+
}
|
|
111
115
|
fs.writeFileSync(this.filePath, JSON.stringify(state, null, 2));
|
|
112
116
|
}
|
|
113
117
|
/**
|
|
@@ -16,6 +16,12 @@ import type { AccountQuota } from "../types/index.js";
|
|
|
16
16
|
* Pure computation — no I/O, no blocking.
|
|
17
17
|
*/
|
|
18
18
|
export declare function parseQuotaHeaders(headers: Headers | Record<string, string>): AccountQuota | null;
|
|
19
|
+
/**
|
|
20
|
+
* Initialise the quota module with a custom file path.
|
|
21
|
+
* When set, all reads/writes go to this path instead of the default
|
|
22
|
+
* ~/.neurolink/account-quotas.json. Call before the first load/save.
|
|
23
|
+
*/
|
|
24
|
+
export declare function initAccountQuota(quotaFilePath: string): void;
|
|
19
25
|
/**
|
|
20
26
|
* Load all persisted account quotas.
|
|
21
27
|
* First call reads from disk; subsequent calls return the in-memory cache.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* updates an in-memory cache and debounces disk writes so the request/response
|
|
10
10
|
* path is never blocked by file I/O.
|
|
11
11
|
*/
|
|
12
|
-
import { join } from "path";
|
|
12
|
+
import { dirname, join } from "path";
|
|
13
13
|
import { homedir } from "os";
|
|
14
14
|
import { promises as fs } from "fs";
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
@@ -73,11 +73,32 @@ let memoryCache = {};
|
|
|
73
73
|
let cacheLoaded = false;
|
|
74
74
|
let dirty = false;
|
|
75
75
|
let flushTimer = null;
|
|
76
|
+
/** Custom quota file path set via initAccountQuota(). */
|
|
77
|
+
let customQuotaFilePath = null;
|
|
78
|
+
/**
|
|
79
|
+
* Initialise the quota module with a custom file path.
|
|
80
|
+
* When set, all reads/writes go to this path instead of the default
|
|
81
|
+
* ~/.neurolink/account-quotas.json. Call before the first load/save.
|
|
82
|
+
*/
|
|
83
|
+
export function initAccountQuota(quotaFilePath) {
|
|
84
|
+
customQuotaFilePath = quotaFilePath;
|
|
85
|
+
// Cancel any pending flush from a previous configuration so it does not
|
|
86
|
+
// write stale data to the new path.
|
|
87
|
+
if (flushTimer) {
|
|
88
|
+
clearTimeout(flushTimer);
|
|
89
|
+
flushTimer = null;
|
|
90
|
+
}
|
|
91
|
+
// Reset cache so the new path is picked up on next load
|
|
92
|
+
memoryCache = {};
|
|
93
|
+
cacheLoaded = false;
|
|
94
|
+
dirty = false;
|
|
95
|
+
}
|
|
76
96
|
function getQuotaFilePath() {
|
|
77
|
-
return join(homedir(), ".neurolink", QUOTA_FILE);
|
|
97
|
+
return customQuotaFilePath ?? join(homedir(), ".neurolink", QUOTA_FILE);
|
|
78
98
|
}
|
|
79
99
|
async function ensureDir() {
|
|
80
|
-
const
|
|
100
|
+
const filePath = getQuotaFilePath();
|
|
101
|
+
const dir = dirname(filePath);
|
|
81
102
|
await fs.mkdir(dir, { recursive: true, mode: 0o700 }).catch(() => {
|
|
82
103
|
// Non-fatal: directory may already exist
|
|
83
104
|
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxy file path resolver.
|
|
3
|
+
*
|
|
4
|
+
* In normal mode, all paths resolve under ~/.neurolink/.
|
|
5
|
+
* In dev mode (--dev), writable paths resolve under <cwd>/.neurolink-dev/
|
|
6
|
+
* so a local dev proxy never touches the global proxy's state.
|
|
7
|
+
*
|
|
8
|
+
* Read-only paths (like .env) always point to the global location
|
|
9
|
+
* since credentials must be shared.
|
|
10
|
+
*
|
|
11
|
+
* NOTE: Claude Code header snapshots (~/.neurolink/header-snapshots/) are
|
|
12
|
+
* not redirected in dev mode. They are only written when a real Claude Code
|
|
13
|
+
* client connects, which typically does not happen during dev testing.
|
|
14
|
+
*/
|
|
15
|
+
export type ProxyPaths = {
|
|
16
|
+
/** Base directory for proxy state files */
|
|
17
|
+
stateDir: string;
|
|
18
|
+
/** logs/ — request/response logs */
|
|
19
|
+
logsDir: string;
|
|
20
|
+
/** account-quotas.json — per-account rate limit state */
|
|
21
|
+
quotaFile: string;
|
|
22
|
+
/** Whether this is a dev-mode isolated instance */
|
|
23
|
+
isDev: boolean;
|
|
24
|
+
};
|
|
25
|
+
export declare function resolveProxyPaths(dev: boolean): ProxyPaths;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxy file path resolver.
|
|
3
|
+
*
|
|
4
|
+
* In normal mode, all paths resolve under ~/.neurolink/.
|
|
5
|
+
* In dev mode (--dev), writable paths resolve under <cwd>/.neurolink-dev/
|
|
6
|
+
* so a local dev proxy never touches the global proxy's state.
|
|
7
|
+
*
|
|
8
|
+
* Read-only paths (like .env) always point to the global location
|
|
9
|
+
* since credentials must be shared.
|
|
10
|
+
*
|
|
11
|
+
* NOTE: Claude Code header snapshots (~/.neurolink/header-snapshots/) are
|
|
12
|
+
* not redirected in dev mode. They are only written when a real Claude Code
|
|
13
|
+
* client connects, which typically does not happen during dev testing.
|
|
14
|
+
*/
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
export function resolveProxyPaths(dev) {
|
|
18
|
+
if (dev) {
|
|
19
|
+
const base = join(process.cwd(), ".neurolink-dev");
|
|
20
|
+
return {
|
|
21
|
+
stateDir: base,
|
|
22
|
+
logsDir: join(base, "logs"),
|
|
23
|
+
quotaFile: join(base, "account-quotas.json"),
|
|
24
|
+
isDev: true,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const base = join(homedir(), ".neurolink");
|
|
28
|
+
return {
|
|
29
|
+
stateDir: base,
|
|
30
|
+
logsDir: join(base, "logs"),
|
|
31
|
+
quotaFile: join(base, "account-quotas.json"),
|
|
32
|
+
isDev: false,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=proxyPaths.js.map
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Useful for debugging and auditing proxy traffic.
|
|
7
7
|
*/
|
|
8
8
|
import type { RequestAttemptLogEntry, RequestLogEntry } from "../types/index.js";
|
|
9
|
-
export declare function initRequestLogger(enabled?: boolean): void;
|
|
9
|
+
export declare function initRequestLogger(enabled?: boolean, customLogsDir?: string): void;
|
|
10
10
|
export declare function logRequest(entry: RequestLogEntry): Promise<void>;
|
|
11
11
|
/**
|
|
12
12
|
* Log an upstream attempt separately from the final request outcome.
|
|
@@ -44,13 +44,13 @@ const SENSITIVE_HEADER_NAMES = new Set([
|
|
|
44
44
|
const SENSITIVE_HEADER_PATTERN = /token|secret|key|password|credential/i;
|
|
45
45
|
/** JSON keys whose values should be redacted in request/response bodies. */
|
|
46
46
|
const SENSITIVE_BODY_KEYS = /("(?:password|access_token|refresh_token|api_key|apiKey|secret|authorization|token|credential|x-api-key)"\s*:\s*)"(?:[^"\\]|\\.)*"/gi;
|
|
47
|
-
export function initRequestLogger(enabled = true) {
|
|
47
|
+
export function initRequestLogger(enabled = true, customLogsDir) {
|
|
48
48
|
logEnabled = enabled;
|
|
49
49
|
if (!enabled) {
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
try {
|
|
53
|
-
logDir = join(homedir(), ".neurolink", "logs");
|
|
53
|
+
logDir = customLogsDir ?? join(homedir(), ".neurolink", "logs");
|
|
54
54
|
if (!existsSync(logDir)) {
|
|
55
55
|
mkdirSync(logDir, { recursive: true, mode: 0o700 });
|
|
56
56
|
}
|
package/dist/lib/types/cli.d.ts
CHANGED
|
@@ -16,6 +16,12 @@ import type { AccountQuota } from "../types/index.js";
|
|
|
16
16
|
* Pure computation — no I/O, no blocking.
|
|
17
17
|
*/
|
|
18
18
|
export declare function parseQuotaHeaders(headers: Headers | Record<string, string>): AccountQuota | null;
|
|
19
|
+
/**
|
|
20
|
+
* Initialise the quota module with a custom file path.
|
|
21
|
+
* When set, all reads/writes go to this path instead of the default
|
|
22
|
+
* ~/.neurolink/account-quotas.json. Call before the first load/save.
|
|
23
|
+
*/
|
|
24
|
+
export declare function initAccountQuota(quotaFilePath: string): void;
|
|
19
25
|
/**
|
|
20
26
|
* Load all persisted account quotas.
|
|
21
27
|
* First call reads from disk; subsequent calls return the in-memory cache.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* updates an in-memory cache and debounces disk writes so the request/response
|
|
10
10
|
* path is never blocked by file I/O.
|
|
11
11
|
*/
|
|
12
|
-
import { join } from "path";
|
|
12
|
+
import { dirname, join } from "path";
|
|
13
13
|
import { homedir } from "os";
|
|
14
14
|
import { promises as fs } from "fs";
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
@@ -73,11 +73,32 @@ let memoryCache = {};
|
|
|
73
73
|
let cacheLoaded = false;
|
|
74
74
|
let dirty = false;
|
|
75
75
|
let flushTimer = null;
|
|
76
|
+
/** Custom quota file path set via initAccountQuota(). */
|
|
77
|
+
let customQuotaFilePath = null;
|
|
78
|
+
/**
|
|
79
|
+
* Initialise the quota module with a custom file path.
|
|
80
|
+
* When set, all reads/writes go to this path instead of the default
|
|
81
|
+
* ~/.neurolink/account-quotas.json. Call before the first load/save.
|
|
82
|
+
*/
|
|
83
|
+
export function initAccountQuota(quotaFilePath) {
|
|
84
|
+
customQuotaFilePath = quotaFilePath;
|
|
85
|
+
// Cancel any pending flush from a previous configuration so it does not
|
|
86
|
+
// write stale data to the new path.
|
|
87
|
+
if (flushTimer) {
|
|
88
|
+
clearTimeout(flushTimer);
|
|
89
|
+
flushTimer = null;
|
|
90
|
+
}
|
|
91
|
+
// Reset cache so the new path is picked up on next load
|
|
92
|
+
memoryCache = {};
|
|
93
|
+
cacheLoaded = false;
|
|
94
|
+
dirty = false;
|
|
95
|
+
}
|
|
76
96
|
function getQuotaFilePath() {
|
|
77
|
-
return join(homedir(), ".neurolink", QUOTA_FILE);
|
|
97
|
+
return customQuotaFilePath ?? join(homedir(), ".neurolink", QUOTA_FILE);
|
|
78
98
|
}
|
|
79
99
|
async function ensureDir() {
|
|
80
|
-
const
|
|
100
|
+
const filePath = getQuotaFilePath();
|
|
101
|
+
const dir = dirname(filePath);
|
|
81
102
|
await fs.mkdir(dir, { recursive: true, mode: 0o700 }).catch(() => {
|
|
82
103
|
// Non-fatal: directory may already exist
|
|
83
104
|
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxy file path resolver.
|
|
3
|
+
*
|
|
4
|
+
* In normal mode, all paths resolve under ~/.neurolink/.
|
|
5
|
+
* In dev mode (--dev), writable paths resolve under <cwd>/.neurolink-dev/
|
|
6
|
+
* so a local dev proxy never touches the global proxy's state.
|
|
7
|
+
*
|
|
8
|
+
* Read-only paths (like .env) always point to the global location
|
|
9
|
+
* since credentials must be shared.
|
|
10
|
+
*
|
|
11
|
+
* NOTE: Claude Code header snapshots (~/.neurolink/header-snapshots/) are
|
|
12
|
+
* not redirected in dev mode. They are only written when a real Claude Code
|
|
13
|
+
* client connects, which typically does not happen during dev testing.
|
|
14
|
+
*/
|
|
15
|
+
export type ProxyPaths = {
|
|
16
|
+
/** Base directory for proxy state files */
|
|
17
|
+
stateDir: string;
|
|
18
|
+
/** logs/ — request/response logs */
|
|
19
|
+
logsDir: string;
|
|
20
|
+
/** account-quotas.json — per-account rate limit state */
|
|
21
|
+
quotaFile: string;
|
|
22
|
+
/** Whether this is a dev-mode isolated instance */
|
|
23
|
+
isDev: boolean;
|
|
24
|
+
};
|
|
25
|
+
export declare function resolveProxyPaths(dev: boolean): ProxyPaths;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxy file path resolver.
|
|
3
|
+
*
|
|
4
|
+
* In normal mode, all paths resolve under ~/.neurolink/.
|
|
5
|
+
* In dev mode (--dev), writable paths resolve under <cwd>/.neurolink-dev/
|
|
6
|
+
* so a local dev proxy never touches the global proxy's state.
|
|
7
|
+
*
|
|
8
|
+
* Read-only paths (like .env) always point to the global location
|
|
9
|
+
* since credentials must be shared.
|
|
10
|
+
*
|
|
11
|
+
* NOTE: Claude Code header snapshots (~/.neurolink/header-snapshots/) are
|
|
12
|
+
* not redirected in dev mode. They are only written when a real Claude Code
|
|
13
|
+
* client connects, which typically does not happen during dev testing.
|
|
14
|
+
*/
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
export function resolveProxyPaths(dev) {
|
|
18
|
+
if (dev) {
|
|
19
|
+
const base = join(process.cwd(), ".neurolink-dev");
|
|
20
|
+
return {
|
|
21
|
+
stateDir: base,
|
|
22
|
+
logsDir: join(base, "logs"),
|
|
23
|
+
quotaFile: join(base, "account-quotas.json"),
|
|
24
|
+
isDev: true,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const base = join(homedir(), ".neurolink");
|
|
28
|
+
return {
|
|
29
|
+
stateDir: base,
|
|
30
|
+
logsDir: join(base, "logs"),
|
|
31
|
+
quotaFile: join(base, "account-quotas.json"),
|
|
32
|
+
isDev: false,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Useful for debugging and auditing proxy traffic.
|
|
7
7
|
*/
|
|
8
8
|
import type { RequestAttemptLogEntry, RequestLogEntry } from "../types/index.js";
|
|
9
|
-
export declare function initRequestLogger(enabled?: boolean): void;
|
|
9
|
+
export declare function initRequestLogger(enabled?: boolean, customLogsDir?: string): void;
|
|
10
10
|
export declare function logRequest(entry: RequestLogEntry): Promise<void>;
|
|
11
11
|
/**
|
|
12
12
|
* Log an upstream attempt separately from the final request outcome.
|
|
@@ -44,13 +44,13 @@ const SENSITIVE_HEADER_NAMES = new Set([
|
|
|
44
44
|
const SENSITIVE_HEADER_PATTERN = /token|secret|key|password|credential/i;
|
|
45
45
|
/** JSON keys whose values should be redacted in request/response bodies. */
|
|
46
46
|
const SENSITIVE_BODY_KEYS = /("(?:password|access_token|refresh_token|api_key|apiKey|secret|authorization|token|credential|x-api-key)"\s*:\s*)"(?:[^"\\]|\\.)*"/gi;
|
|
47
|
-
export function initRequestLogger(enabled = true) {
|
|
47
|
+
export function initRequestLogger(enabled = true, customLogsDir) {
|
|
48
48
|
logEnabled = enabled;
|
|
49
49
|
if (!enabled) {
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
try {
|
|
53
|
-
logDir = join(homedir(), ".neurolink", "logs");
|
|
53
|
+
logDir = customLogsDir ?? join(homedir(), ".neurolink", "logs");
|
|
54
54
|
if (!existsSync(logDir)) {
|
|
55
55
|
mkdirSync(logDir, { recursive: true, mode: 0o700 });
|
|
56
56
|
}
|
package/dist/types/cli.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.51.0",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"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.",
|
|
6
6
|
"author": {
|