better-gemini-mcp 1.0.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.
Potentially problematic release.
This version of better-gemini-mcp might be problematic. Click here for more details.
- package/CHANGELOG.md +57 -0
- package/LICENSE.md +13 -0
- package/README.md +291 -0
- package/dist/constants.d.ts +148 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +295 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +280 -0
- package/dist/index.js.map +1 -0
- package/dist/setup/index.d.ts +7 -0
- package/dist/setup/index.d.ts.map +1 -0
- package/dist/setup/index.js +6 -0
- package/dist/setup/index.js.map +1 -0
- package/dist/setup/wizard.d.ts +39 -0
- package/dist/setup/wizard.d.ts.map +1 -0
- package/dist/setup/wizard.js +222 -0
- package/dist/setup/wizard.js.map +1 -0
- package/dist/tools/analyze-directory.tool.d.ts +8 -0
- package/dist/tools/analyze-directory.tool.d.ts.map +1 -0
- package/dist/tools/analyze-directory.tool.js +195 -0
- package/dist/tools/analyze-directory.tool.js.map +1 -0
- package/dist/tools/deep-research.tool.d.ts +8 -0
- package/dist/tools/deep-research.tool.d.ts.map +1 -0
- package/dist/tools/deep-research.tool.js +153 -0
- package/dist/tools/deep-research.tool.js.map +1 -0
- package/dist/tools/fetch-chunk.tool.d.ts +8 -0
- package/dist/tools/fetch-chunk.tool.d.ts.map +1 -0
- package/dist/tools/fetch-chunk.tool.js +123 -0
- package/dist/tools/fetch-chunk.tool.js.map +1 -0
- package/dist/tools/health-check.tool.d.ts +8 -0
- package/dist/tools/health-check.tool.d.ts.map +1 -0
- package/dist/tools/health-check.tool.js +113 -0
- package/dist/tools/health-check.tool.js.map +1 -0
- package/dist/tools/index.d.ts +16 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +35 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/quick-query.tool.d.ts +8 -0
- package/dist/tools/quick-query.tool.d.ts.map +1 -0
- package/dist/tools/quick-query.tool.js +154 -0
- package/dist/tools/quick-query.tool.js.map +1 -0
- package/dist/tools/registry.d.ts +52 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +95 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/validate-paths.tool.d.ts +8 -0
- package/dist/tools/validate-paths.tool.d.ts.map +1 -0
- package/dist/tools/validate-paths.tool.js +64 -0
- package/dist/tools/validate-paths.tool.js.map +1 -0
- package/dist/types.d.ts +221 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/commandExecutor.d.ts +28 -0
- package/dist/utils/commandExecutor.d.ts.map +1 -0
- package/dist/utils/commandExecutor.js +105 -0
- package/dist/utils/commandExecutor.js.map +1 -0
- package/dist/utils/geminiExecutor.d.ts +71 -0
- package/dist/utils/geminiExecutor.d.ts.map +1 -0
- package/dist/utils/geminiExecutor.js +281 -0
- package/dist/utils/geminiExecutor.js.map +1 -0
- package/dist/utils/ignorePatterns.d.ts +69 -0
- package/dist/utils/ignorePatterns.d.ts.map +1 -0
- package/dist/utils/ignorePatterns.js +178 -0
- package/dist/utils/ignorePatterns.js.map +1 -0
- package/dist/utils/index.d.ts +11 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +39 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +160 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/pathValidator.d.ts +55 -0
- package/dist/utils/pathValidator.d.ts.map +1 -0
- package/dist/utils/pathValidator.js +137 -0
- package/dist/utils/pathValidator.js.map +1 -0
- package/dist/utils/responseCache.d.ts +80 -0
- package/dist/utils/responseCache.d.ts.map +1 -0
- package/dist/utils/responseCache.js +179 -0
- package/dist/utils/responseCache.js.map +1 -0
- package/dist/utils/responseChunker.d.ts +36 -0
- package/dist/utils/responseChunker.d.ts.map +1 -0
- package/dist/utils/responseChunker.js +96 -0
- package/dist/utils/responseChunker.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured logging utility for Better Gemini MCP Server
|
|
3
|
+
* Implements logging with levels: error, warn, info, debug
|
|
4
|
+
* NEVER logs sensitive credentials like GEMINI_API_KEY
|
|
5
|
+
*/
|
|
6
|
+
import { LOG_PREFIX } from "../constants.js";
|
|
7
|
+
/**
|
|
8
|
+
* List of sensitive patterns that should be filtered from logs
|
|
9
|
+
*/
|
|
10
|
+
const SENSITIVE_PATTERNS = [
|
|
11
|
+
/GEMINI_API_KEY[=:]\s*["']?[^"'\s]+["']?/gi,
|
|
12
|
+
/api[_-]?key[=:]\s*["']?[^"'\s]+["']?/gi,
|
|
13
|
+
/authorization[=:]\s*["']?[^"'\s]+["']?/gi,
|
|
14
|
+
/bearer\s+[a-zA-Z0-9_-]+/gi,
|
|
15
|
+
/token[=:]\s*["']?[^"'\s]+["']?/gi,
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Check if debug logging is enabled via DEBUG env var
|
|
19
|
+
*/
|
|
20
|
+
function isDebugEnabled() {
|
|
21
|
+
return process.env.DEBUG === "true" || process.env.DEBUG === "1";
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get ISO timestamp for log entries
|
|
25
|
+
*/
|
|
26
|
+
function getTimestamp() {
|
|
27
|
+
return new Date().toISOString();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Sanitize a message by removing sensitive credential patterns
|
|
31
|
+
*/
|
|
32
|
+
function sanitize(message) {
|
|
33
|
+
let sanitized = message;
|
|
34
|
+
for (const pattern of SENSITIVE_PATTERNS) {
|
|
35
|
+
sanitized = sanitized.replace(pattern, "[REDACTED]");
|
|
36
|
+
}
|
|
37
|
+
return sanitized;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Format a log message with timestamp, level, and prefix
|
|
41
|
+
*/
|
|
42
|
+
function formatMessage(level, message) {
|
|
43
|
+
const sanitizedMessage = sanitize(message);
|
|
44
|
+
return `[${getTimestamp()}] [${level.toUpperCase()}] ${LOG_PREFIX} ${sanitizedMessage}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Safely stringify any value for logging, handling circular references
|
|
48
|
+
*/
|
|
49
|
+
function safeStringify(value) {
|
|
50
|
+
if (value === undefined)
|
|
51
|
+
return "undefined";
|
|
52
|
+
if (value === null)
|
|
53
|
+
return "null";
|
|
54
|
+
if (typeof value === "string")
|
|
55
|
+
return value;
|
|
56
|
+
if (value instanceof Error) {
|
|
57
|
+
return `${value.name}: ${value.message}${value.stack ? `\n${value.stack}` : ""}`;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
return JSON.stringify(value, null, 2);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return String(value);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Logger class for structured logging
|
|
68
|
+
*/
|
|
69
|
+
export class Logger {
|
|
70
|
+
/**
|
|
71
|
+
* Log an error message (always logged)
|
|
72
|
+
*/
|
|
73
|
+
static error(message, ...args) {
|
|
74
|
+
const formatted = formatMessage("error", message);
|
|
75
|
+
if (args.length > 0) {
|
|
76
|
+
const argsStr = args.map((arg) => sanitize(safeStringify(arg))).join(" ");
|
|
77
|
+
console.error(formatted, argsStr);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.error(formatted);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Log a warning message (always logged)
|
|
85
|
+
*/
|
|
86
|
+
static warn(message, ...args) {
|
|
87
|
+
const formatted = formatMessage("warn", message);
|
|
88
|
+
if (args.length > 0) {
|
|
89
|
+
const argsStr = args.map((arg) => sanitize(safeStringify(arg))).join(" ");
|
|
90
|
+
console.error(formatted, argsStr);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
console.error(formatted);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Log an info message (always logged)
|
|
98
|
+
*/
|
|
99
|
+
static info(message, ...args) {
|
|
100
|
+
const formatted = formatMessage("info", message);
|
|
101
|
+
if (args.length > 0) {
|
|
102
|
+
const argsStr = args.map((arg) => sanitize(safeStringify(arg))).join(" ");
|
|
103
|
+
console.error(formatted, argsStr);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
console.error(formatted);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Log a debug message (only when DEBUG env var is set)
|
|
111
|
+
*/
|
|
112
|
+
static debug(message, ...args) {
|
|
113
|
+
if (!isDebugEnabled())
|
|
114
|
+
return;
|
|
115
|
+
const formatted = formatMessage("debug", message);
|
|
116
|
+
if (args.length > 0) {
|
|
117
|
+
const argsStr = args.map((arg) => sanitize(safeStringify(arg))).join(" ");
|
|
118
|
+
console.error(formatted, argsStr);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
console.error(formatted);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Log a tool invocation (sanitized parameters, excludes full prompts)
|
|
126
|
+
*/
|
|
127
|
+
static toolInvocation(toolName, args) {
|
|
128
|
+
// Create a copy with truncated prompt for logging
|
|
129
|
+
const sanitizedArgs = { ...args };
|
|
130
|
+
if (typeof sanitizedArgs.prompt === "string" && sanitizedArgs.prompt.length > 100) {
|
|
131
|
+
sanitizedArgs.prompt = sanitizedArgs.prompt.substring(0, 100) + "...[truncated]";
|
|
132
|
+
}
|
|
133
|
+
this.info(`Tool invocation: ${toolName}`, sanitizedArgs);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Log command execution (command args, not output)
|
|
137
|
+
*/
|
|
138
|
+
static commandExecution(command, args, startTime) {
|
|
139
|
+
// Filter out the actual prompt content from args (it's the last argument after -p)
|
|
140
|
+
const safeArgs = args.map((arg, index) => {
|
|
141
|
+
// If previous arg was -p or --prompt, truncate this arg
|
|
142
|
+
if (index > 0 && (args[index - 1] === "-p" || args[index - 1] === "--prompt")) {
|
|
143
|
+
return arg.length > 100 ? arg.substring(0, 100) + "...[truncated]" : arg;
|
|
144
|
+
}
|
|
145
|
+
return arg;
|
|
146
|
+
});
|
|
147
|
+
this.debug(`[${startTime}] Executing: ${command} ${safeArgs.join(" ")}`);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Log command completion with timing
|
|
151
|
+
*/
|
|
152
|
+
static commandComplete(startTime, exitCode, outputLength) {
|
|
153
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
154
|
+
this.debug(`[${elapsed}s] Process finished with exit code: ${exitCode}`);
|
|
155
|
+
if (outputLength !== undefined) {
|
|
156
|
+
this.debug(`Response length: ${outputLength} chars`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path validation utility for security enforcement
|
|
3
|
+
* Ensures all paths are within project root (PRD §6.1, §6.2)
|
|
4
|
+
*/
|
|
5
|
+
import type { PathValidationResult } from "../types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Validate a path for security and existence
|
|
8
|
+
*
|
|
9
|
+
* @param inputPath - The path to validate (relative or absolute)
|
|
10
|
+
* @param projectRoot - The project root directory (absolute path)
|
|
11
|
+
* @returns PathValidationResult with validation details
|
|
12
|
+
*/
|
|
13
|
+
export declare function validatePath(inputPath: string, projectRoot: string): PathValidationResult;
|
|
14
|
+
/**
|
|
15
|
+
* Check if an absolute path is within the project root
|
|
16
|
+
*
|
|
17
|
+
* @param absolutePath - The absolute path to check
|
|
18
|
+
* @param projectRoot - The project root directory (absolute path)
|
|
19
|
+
* @returns true if the path is within project root
|
|
20
|
+
*/
|
|
21
|
+
export declare function isWithinProjectRoot(absolutePath: string, projectRoot: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Extract @path references from a prompt string
|
|
24
|
+
*
|
|
25
|
+
* @param prompt - The prompt string to scan
|
|
26
|
+
* @returns Array of path strings found in the prompt
|
|
27
|
+
*/
|
|
28
|
+
export declare function extractAtPathReferences(prompt: string): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Validate all @path references in a prompt
|
|
31
|
+
*
|
|
32
|
+
* @param prompt - The prompt string to validate
|
|
33
|
+
* @param projectRoot - The project root directory
|
|
34
|
+
* @returns Array of validation results for each @path reference
|
|
35
|
+
*/
|
|
36
|
+
export declare function validatePromptPaths(prompt: string, projectRoot: string): PathValidationResult[];
|
|
37
|
+
/**
|
|
38
|
+
* Check if a prompt contains any invalid @path references
|
|
39
|
+
*
|
|
40
|
+
* @param prompt - The prompt string to check
|
|
41
|
+
* @param projectRoot - The project root directory
|
|
42
|
+
* @returns Object with isValid flag and array of invalid paths
|
|
43
|
+
*/
|
|
44
|
+
export declare function checkPromptPathsValid(prompt: string, projectRoot: string): {
|
|
45
|
+
isValid: boolean;
|
|
46
|
+
invalidPaths: PathValidationResult[];
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Get the project root directory
|
|
50
|
+
* Uses PROJECT_ROOT env var if set, otherwise process.cwd()
|
|
51
|
+
*
|
|
52
|
+
* @returns The project root directory (absolute path)
|
|
53
|
+
*/
|
|
54
|
+
export declare function getProjectRoot(): string;
|
|
55
|
+
//# sourceMappingURL=pathValidator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathValidator.d.ts","sourceRoot":"","sources":["../../src/utils/pathValidator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAQxD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,oBAAoB,CAiDzF;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CActF;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAYhE;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB,oBAAoB,EAAE,CAGxB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAClB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,oBAAoB,EAAE,CAAA;CAAE,CAQ5D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path validation utility for security enforcement
|
|
3
|
+
* Ensures all paths are within project root (PRD §6.1, §6.2)
|
|
4
|
+
*/
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
/**
|
|
8
|
+
* Regex pattern to extract @path references from prompts
|
|
9
|
+
* Matches @<path> patterns (e.g., @src/auth.ts, @./config.json)
|
|
10
|
+
*/
|
|
11
|
+
const AT_PATH_PATTERN = /@([\w./-]+)/g;
|
|
12
|
+
/**
|
|
13
|
+
* Validate a path for security and existence
|
|
14
|
+
*
|
|
15
|
+
* @param inputPath - The path to validate (relative or absolute)
|
|
16
|
+
* @param projectRoot - The project root directory (absolute path)
|
|
17
|
+
* @returns PathValidationResult with validation details
|
|
18
|
+
*/
|
|
19
|
+
export function validatePath(inputPath, projectRoot) {
|
|
20
|
+
// Normalize the project root
|
|
21
|
+
const normalizedRoot = path.resolve(projectRoot);
|
|
22
|
+
// Resolve the input path relative to project root
|
|
23
|
+
let resolved;
|
|
24
|
+
if (path.isAbsolute(inputPath)) {
|
|
25
|
+
resolved = path.resolve(inputPath);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
resolved = path.resolve(normalizedRoot, inputPath);
|
|
29
|
+
}
|
|
30
|
+
// Normalize to handle any .. or . in the path
|
|
31
|
+
resolved = path.normalize(resolved);
|
|
32
|
+
// Check if path is within project root
|
|
33
|
+
const allowed = isWithinProjectRoot(resolved, normalizedRoot);
|
|
34
|
+
// Check if path exists
|
|
35
|
+
let exists = false;
|
|
36
|
+
if (allowed) {
|
|
37
|
+
try {
|
|
38
|
+
fs.accessSync(resolved);
|
|
39
|
+
exists = true;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
exists = false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Build result
|
|
46
|
+
const result = {
|
|
47
|
+
input: inputPath,
|
|
48
|
+
resolved,
|
|
49
|
+
exists,
|
|
50
|
+
allowed,
|
|
51
|
+
};
|
|
52
|
+
// Add reason for disallowed paths
|
|
53
|
+
if (!allowed) {
|
|
54
|
+
if (inputPath.includes("..")) {
|
|
55
|
+
result.reason = "Path contains parent directory traversal (..)";
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
result.reason = "Path is outside project root";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else if (!exists) {
|
|
62
|
+
result.reason = "Path does not exist";
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Check if an absolute path is within the project root
|
|
68
|
+
*
|
|
69
|
+
* @param absolutePath - The absolute path to check
|
|
70
|
+
* @param projectRoot - The project root directory (absolute path)
|
|
71
|
+
* @returns true if the path is within project root
|
|
72
|
+
*/
|
|
73
|
+
export function isWithinProjectRoot(absolutePath, projectRoot) {
|
|
74
|
+
// Normalize both paths
|
|
75
|
+
const normalizedPath = path.normalize(absolutePath);
|
|
76
|
+
const normalizedRoot = path.normalize(projectRoot);
|
|
77
|
+
// Check if the path starts with the project root
|
|
78
|
+
// Add trailing separator to avoid matching partial directory names
|
|
79
|
+
// e.g., /home/user/project-test should not match /home/user/project
|
|
80
|
+
const rootWithSep = normalizedRoot.endsWith(path.sep)
|
|
81
|
+
? normalizedRoot
|
|
82
|
+
: normalizedRoot + path.sep;
|
|
83
|
+
// Path is within root if it equals root or starts with root + separator
|
|
84
|
+
return normalizedPath === normalizedRoot || normalizedPath.startsWith(rootWithSep);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Extract @path references from a prompt string
|
|
88
|
+
*
|
|
89
|
+
* @param prompt - The prompt string to scan
|
|
90
|
+
* @returns Array of path strings found in the prompt
|
|
91
|
+
*/
|
|
92
|
+
export function extractAtPathReferences(prompt) {
|
|
93
|
+
const matches = [];
|
|
94
|
+
let match;
|
|
95
|
+
// Reset regex state
|
|
96
|
+
AT_PATH_PATTERN.lastIndex = 0;
|
|
97
|
+
while ((match = AT_PATH_PATTERN.exec(prompt)) !== null) {
|
|
98
|
+
matches.push(match[1]);
|
|
99
|
+
}
|
|
100
|
+
return matches;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Validate all @path references in a prompt
|
|
104
|
+
*
|
|
105
|
+
* @param prompt - The prompt string to validate
|
|
106
|
+
* @param projectRoot - The project root directory
|
|
107
|
+
* @returns Array of validation results for each @path reference
|
|
108
|
+
*/
|
|
109
|
+
export function validatePromptPaths(prompt, projectRoot) {
|
|
110
|
+
const paths = extractAtPathReferences(prompt);
|
|
111
|
+
return paths.map((p) => validatePath(p, projectRoot));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check if a prompt contains any invalid @path references
|
|
115
|
+
*
|
|
116
|
+
* @param prompt - The prompt string to check
|
|
117
|
+
* @param projectRoot - The project root directory
|
|
118
|
+
* @returns Object with isValid flag and array of invalid paths
|
|
119
|
+
*/
|
|
120
|
+
export function checkPromptPathsValid(prompt, projectRoot) {
|
|
121
|
+
const results = validatePromptPaths(prompt, projectRoot);
|
|
122
|
+
const invalidPaths = results.filter((r) => !r.allowed);
|
|
123
|
+
return {
|
|
124
|
+
isValid: invalidPaths.length === 0,
|
|
125
|
+
invalidPaths,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get the project root directory
|
|
130
|
+
* Uses PROJECT_ROOT env var if set, otherwise process.cwd()
|
|
131
|
+
*
|
|
132
|
+
* @returns The project root directory (absolute path)
|
|
133
|
+
*/
|
|
134
|
+
export function getProjectRoot() {
|
|
135
|
+
return process.env.PROJECT_ROOT || process.cwd();
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=pathValidator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathValidator.js","sourceRoot":"","sources":["../../src/utils/pathValidator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response cache utility for chunked responses
|
|
3
|
+
* Implements in-memory cache with 1-hour TTL (PRD §5.7)
|
|
4
|
+
*/
|
|
5
|
+
import type { CacheEntry, CachedChunk } from "../types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Generate a unique cache key
|
|
8
|
+
*
|
|
9
|
+
* @returns Cache key in format "cache_<random>"
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateCacheKey(): string;
|
|
12
|
+
/**
|
|
13
|
+
* Store chunked response in cache
|
|
14
|
+
*
|
|
15
|
+
* @param chunks - Array of cached chunks to store
|
|
16
|
+
* @param ttlMs - Time-to-live in milliseconds (default: 1 hour)
|
|
17
|
+
* @returns The generated cache key
|
|
18
|
+
*/
|
|
19
|
+
export declare function cacheResponse(chunks: CachedChunk[], ttlMs?: number): string;
|
|
20
|
+
/**
|
|
21
|
+
* Retrieve cached response by key
|
|
22
|
+
*
|
|
23
|
+
* @param key - The cache key
|
|
24
|
+
* @returns CacheEntry if found and not expired, null otherwise
|
|
25
|
+
*/
|
|
26
|
+
export declare function getResponse(key: string): CacheEntry | null;
|
|
27
|
+
/**
|
|
28
|
+
* Get a specific chunk from cached response
|
|
29
|
+
*
|
|
30
|
+
* @param key - The cache key
|
|
31
|
+
* @param chunkIndex - 1-based chunk index
|
|
32
|
+
* @returns The chunk if found, null otherwise
|
|
33
|
+
*/
|
|
34
|
+
export declare function getChunk(key: string, chunkIndex: number): CachedChunk | null;
|
|
35
|
+
/**
|
|
36
|
+
* Check if a cache key exists and is valid
|
|
37
|
+
*
|
|
38
|
+
* @param key - The cache key
|
|
39
|
+
* @returns true if key exists and not expired
|
|
40
|
+
*/
|
|
41
|
+
export declare function hasValidCache(key: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Get cache entry metadata without full content
|
|
44
|
+
*
|
|
45
|
+
* @param key - The cache key
|
|
46
|
+
* @returns Metadata object or null if not found
|
|
47
|
+
*/
|
|
48
|
+
export declare function getCacheMetadata(key: string): {
|
|
49
|
+
totalChunks: number;
|
|
50
|
+
expiresAt: Date;
|
|
51
|
+
createdAt: Date;
|
|
52
|
+
} | null;
|
|
53
|
+
/**
|
|
54
|
+
* Delete a cache entry
|
|
55
|
+
*
|
|
56
|
+
* @param key - The cache key to delete
|
|
57
|
+
* @returns true if entry was deleted
|
|
58
|
+
*/
|
|
59
|
+
export declare function deleteCache(key: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Clear all expired cache entries
|
|
62
|
+
*
|
|
63
|
+
* @returns Number of entries removed
|
|
64
|
+
*/
|
|
65
|
+
export declare function clearExpired(): number;
|
|
66
|
+
/**
|
|
67
|
+
* Clear the entire cache
|
|
68
|
+
*/
|
|
69
|
+
export declare function clearAll(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Get current cache statistics
|
|
72
|
+
*
|
|
73
|
+
* @returns Object with cache stats
|
|
74
|
+
*/
|
|
75
|
+
export declare function getCacheStats(): {
|
|
76
|
+
size: number;
|
|
77
|
+
oldestEntry: Date | null;
|
|
78
|
+
newestEntry: Date | null;
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=responseCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responseCache.d.ts","sourceRoot":"","sources":["../../src/utils/responseCache.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQ3D;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAIzC;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,WAAW,EAAE,EACrB,KAAK,GAAE,MAA8B,GACpC,MAAM,CAgBR;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAc1D;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAa5E;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,GACV;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAE,GAAG,IAAI,CAYlE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAYrC;AAED;;GAEG;AACH,wBAAgB,QAAQ,IAAI,IAAI,CAE/B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;CAC1B,CAkBA"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response cache utility for chunked responses
|
|
3
|
+
* Implements in-memory cache with 1-hour TTL (PRD §5.7)
|
|
4
|
+
*/
|
|
5
|
+
import { DEFAULTS } from "../constants.js";
|
|
6
|
+
/**
|
|
7
|
+
* In-memory cache storage
|
|
8
|
+
*/
|
|
9
|
+
const cache = new Map();
|
|
10
|
+
/**
|
|
11
|
+
* Generate a unique cache key
|
|
12
|
+
*
|
|
13
|
+
* @returns Cache key in format "cache_<random>"
|
|
14
|
+
*/
|
|
15
|
+
export function generateCacheKey() {
|
|
16
|
+
const random = Math.random().toString(36).substring(2, 10);
|
|
17
|
+
const timestamp = Date.now().toString(36);
|
|
18
|
+
return `cache_${timestamp}${random}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Store chunked response in cache
|
|
22
|
+
*
|
|
23
|
+
* @param chunks - Array of cached chunks to store
|
|
24
|
+
* @param ttlMs - Time-to-live in milliseconds (default: 1 hour)
|
|
25
|
+
* @returns The generated cache key
|
|
26
|
+
*/
|
|
27
|
+
export function cacheResponse(chunks, ttlMs = DEFAULTS.CACHE_TTL_MS) {
|
|
28
|
+
const key = generateCacheKey();
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
const entry = {
|
|
31
|
+
chunks,
|
|
32
|
+
createdAt: now,
|
|
33
|
+
expiresAt: now + ttlMs,
|
|
34
|
+
};
|
|
35
|
+
cache.set(key, entry);
|
|
36
|
+
// Schedule cleanup of expired entries
|
|
37
|
+
scheduleCleanup();
|
|
38
|
+
return key;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Retrieve cached response by key
|
|
42
|
+
*
|
|
43
|
+
* @param key - The cache key
|
|
44
|
+
* @returns CacheEntry if found and not expired, null otherwise
|
|
45
|
+
*/
|
|
46
|
+
export function getResponse(key) {
|
|
47
|
+
const entry = cache.get(key);
|
|
48
|
+
if (!entry) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
// Check if expired
|
|
52
|
+
if (Date.now() > entry.expiresAt) {
|
|
53
|
+
cache.delete(key);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return entry;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get a specific chunk from cached response
|
|
60
|
+
*
|
|
61
|
+
* @param key - The cache key
|
|
62
|
+
* @param chunkIndex - 1-based chunk index
|
|
63
|
+
* @returns The chunk if found, null otherwise
|
|
64
|
+
*/
|
|
65
|
+
export function getChunk(key, chunkIndex) {
|
|
66
|
+
const entry = getResponse(key);
|
|
67
|
+
if (!entry) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
// Validate chunk index (1-based)
|
|
71
|
+
if (chunkIndex < 1 || chunkIndex > entry.chunks.length) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
return entry.chunks[chunkIndex - 1];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if a cache key exists and is valid
|
|
78
|
+
*
|
|
79
|
+
* @param key - The cache key
|
|
80
|
+
* @returns true if key exists and not expired
|
|
81
|
+
*/
|
|
82
|
+
export function hasValidCache(key) {
|
|
83
|
+
return getResponse(key) !== null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get cache entry metadata without full content
|
|
87
|
+
*
|
|
88
|
+
* @param key - The cache key
|
|
89
|
+
* @returns Metadata object or null if not found
|
|
90
|
+
*/
|
|
91
|
+
export function getCacheMetadata(key) {
|
|
92
|
+
const entry = getResponse(key);
|
|
93
|
+
if (!entry) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
totalChunks: entry.chunks.length,
|
|
98
|
+
expiresAt: new Date(entry.expiresAt),
|
|
99
|
+
createdAt: new Date(entry.createdAt),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Delete a cache entry
|
|
104
|
+
*
|
|
105
|
+
* @param key - The cache key to delete
|
|
106
|
+
* @returns true if entry was deleted
|
|
107
|
+
*/
|
|
108
|
+
export function deleteCache(key) {
|
|
109
|
+
return cache.delete(key);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Clear all expired cache entries
|
|
113
|
+
*
|
|
114
|
+
* @returns Number of entries removed
|
|
115
|
+
*/
|
|
116
|
+
export function clearExpired() {
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
let removed = 0;
|
|
119
|
+
for (const [key, entry] of cache.entries()) {
|
|
120
|
+
if (now > entry.expiresAt) {
|
|
121
|
+
cache.delete(key);
|
|
122
|
+
removed++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return removed;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Clear the entire cache
|
|
129
|
+
*/
|
|
130
|
+
export function clearAll() {
|
|
131
|
+
cache.clear();
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get current cache statistics
|
|
135
|
+
*
|
|
136
|
+
* @returns Object with cache stats
|
|
137
|
+
*/
|
|
138
|
+
export function getCacheStats() {
|
|
139
|
+
let oldest = null;
|
|
140
|
+
let newest = null;
|
|
141
|
+
for (const entry of cache.values()) {
|
|
142
|
+
if (oldest === null || entry.createdAt < oldest) {
|
|
143
|
+
oldest = entry.createdAt;
|
|
144
|
+
}
|
|
145
|
+
if (newest === null || entry.createdAt > newest) {
|
|
146
|
+
newest = entry.createdAt;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
size: cache.size,
|
|
151
|
+
oldestEntry: oldest ? new Date(oldest) : null,
|
|
152
|
+
newestEntry: newest ? new Date(newest) : null,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// Cleanup scheduling
|
|
157
|
+
// ============================================================================
|
|
158
|
+
let cleanupScheduled = false;
|
|
159
|
+
const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
160
|
+
/**
|
|
161
|
+
* Schedule periodic cleanup of expired entries
|
|
162
|
+
*/
|
|
163
|
+
function scheduleCleanup() {
|
|
164
|
+
if (cleanupScheduled) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
cleanupScheduled = true;
|
|
168
|
+
// Use unref() so the timer doesn't keep the process alive
|
|
169
|
+
const timer = setInterval(() => {
|
|
170
|
+
clearExpired();
|
|
171
|
+
// Stop cleanup if cache is empty
|
|
172
|
+
if (cache.size === 0) {
|
|
173
|
+
clearInterval(timer);
|
|
174
|
+
cleanupScheduled = false;
|
|
175
|
+
}
|
|
176
|
+
}, CLEANUP_INTERVAL_MS);
|
|
177
|
+
timer.unref();
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=responseCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responseCache.js","sourceRoot":"","sources":["../../src/utils/responseCache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response chunker utility for splitting large responses
|
|
3
|
+
* Implements chunking with configurable size (PRD §5.2)
|
|
4
|
+
*/
|
|
5
|
+
import type { CachedChunk } from "../types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Chunk a response string into smaller pieces
|
|
8
|
+
*
|
|
9
|
+
* @param response - The full response string to chunk
|
|
10
|
+
* @param chunkSizeKB - Size of each chunk in kilobytes (default: 10KB)
|
|
11
|
+
* @returns Array of CachedChunk objects with metadata
|
|
12
|
+
*/
|
|
13
|
+
export declare function chunkResponse(response: string, chunkSizeKB?: number): CachedChunk[];
|
|
14
|
+
/**
|
|
15
|
+
* Check if a response needs chunking
|
|
16
|
+
*
|
|
17
|
+
* @param response - The response string to check
|
|
18
|
+
* @param chunkSizeKB - Chunk size threshold in KB
|
|
19
|
+
* @returns true if the response exceeds the chunk size
|
|
20
|
+
*/
|
|
21
|
+
export declare function needsChunking(response: string, chunkSizeKB?: number): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Get the number of chunks that would be created for a response
|
|
24
|
+
*
|
|
25
|
+
* @param response - The response string
|
|
26
|
+
* @param chunkSizeKB - Chunk size in KB
|
|
27
|
+
* @returns Estimated number of chunks
|
|
28
|
+
*/
|
|
29
|
+
export declare function estimateChunkCount(response: string, chunkSizeKB?: number): number;
|
|
30
|
+
/**
|
|
31
|
+
* Get the chunk size configuration from environment or default
|
|
32
|
+
*
|
|
33
|
+
* @returns Chunk size in KB
|
|
34
|
+
*/
|
|
35
|
+
export declare function getChunkSizeKB(): number;
|
|
36
|
+
//# sourceMappingURL=responseChunker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responseChunker.d.ts","sourceRoot":"","sources":["../../src/utils/responseChunker.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,WAAW,GAAE,MAAwC,GACpD,WAAW,EAAE,CAuDf;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,WAAW,GAAE,MAAwC,GACpD,OAAO,CAGT;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,WAAW,GAAE,MAAwC,GACpD,MAAM,CAGR;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,CASvC"}
|