@stackmemoryai/stackmemory
Version:
Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.
203 lines (202 loc) • 5.7 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import * as fs from "fs";
import * as path from "path";
const SENSITIVE_PATTERNS = [
/\b(api[_-]?key|apikey)\s*[:=]\s*['"]?[\w-]+['"]?/gi,
/\b(secret|password|token|credential|auth)\s*[:=]\s*['"]?[\w-]+['"]?/gi,
/\b(lin_api_[\w]+)/gi,
/\b(lin_oauth_[\w]+)/gi,
/\b(sk-[\w]+)/gi,
/\b(npm_[\w]+)/gi,
/\b(ghp_[\w]+)/gi,
/\b(ghs_[\w]+)/gi,
/Bearer\s+[\w.-]+/gi,
/Basic\s+[\w=]+/gi,
/postgres(ql)?:\/\/[^@\s]+:[^@\s]+@/gi
];
const SENSITIVE_FIELD_NAMES = [
"password",
"token",
"apikey",
"api_key",
"secret",
"credential",
"authorization",
"auth",
"accesstoken",
"access_token",
"refreshtoken",
"refresh_token"
];
function redactString(input) {
let result = input;
for (const pattern of SENSITIVE_PATTERNS) {
pattern.lastIndex = 0;
result = result.replace(pattern, "[REDACTED]");
}
return result;
}
function sanitizeForLogging(obj) {
if (obj === null || obj === void 0) {
return obj;
}
if (typeof obj === "string") {
return redactString(obj);
}
if (Array.isArray(obj)) {
return obj.map(sanitizeForLogging);
}
if (typeof obj === "object") {
const sanitized = {};
for (const [key, value] of Object.entries(obj)) {
if (SENSITIVE_FIELD_NAMES.some((sf) => key.toLowerCase().includes(sf))) {
sanitized[key] = "[REDACTED]";
} else {
sanitized[key] = sanitizeForLogging(value);
}
}
return sanitized;
}
return obj;
}
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
LogLevel2[LogLevel2["ERROR"] = 0] = "ERROR";
LogLevel2[LogLevel2["WARN"] = 1] = "WARN";
LogLevel2[LogLevel2["INFO"] = 2] = "INFO";
LogLevel2[LogLevel2["DEBUG"] = 3] = "DEBUG";
return LogLevel2;
})(LogLevel || {});
class Logger {
static instance;
logLevel = 2 /* INFO */;
logFile;
fileLoggingDisabledNotified = false;
constructor() {
const envLevel = process.env["STACKMEMORY_LOG_LEVEL"]?.toUpperCase();
switch (envLevel) {
case "ERROR":
this.logLevel = 0 /* ERROR */;
break;
case "WARN":
this.logLevel = 1 /* WARN */;
break;
case "DEBUG":
this.logLevel = 3 /* DEBUG */;
break;
default:
this.logLevel = 2 /* INFO */;
}
if (this.logLevel === 3 /* DEBUG */ || process.env["STACKMEMORY_LOG_FILE"]) {
this.logFile = process.env["STACKMEMORY_LOG_FILE"] || path.join(
process.env["HOME"] || ".",
".stackmemory",
"logs",
"cli.log"
);
this.ensureLogDirectory();
}
}
static getInstance() {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
ensureLogDirectory() {
if (!this.logFile) return;
const logDir = path.dirname(this.logFile);
try {
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });
}
} catch (err) {
this.logFile = void 0;
if (!this.fileLoggingDisabledNotified) {
this.fileLoggingDisabledNotified = true;
const msg = "[Logger] File logging disabled (failed to create log directory). Falling back to console only.";
console.warn(msg);
}
}
}
writeLog(entry) {
const sanitizedEntry = {
...entry,
message: redactString(entry.message),
context: entry.context ? sanitizeForLogging(entry.context) : void 0
};
const logLine = JSON.stringify(sanitizedEntry) + "\n";
if (this.logFile) {
try {
fs.appendFileSync(this.logFile, logLine);
} catch (err) {
this.logFile = void 0;
if (!this.fileLoggingDisabledNotified) {
this.fileLoggingDisabledNotified = true;
const msg = "[Logger] File logging disabled (write failed). Falling back to console only.";
console.warn(msg);
}
}
}
if (entry.level <= this.logLevel) {
const levelNames = ["ERROR", "WARN", "INFO", "DEBUG"];
const levelName = levelNames[entry.level] || "UNKNOWN";
const consoleMessage = `[${entry.timestamp}] ${levelName}: ${entry.message}`;
if (entry.level === 0 /* ERROR */) {
console.error(consoleMessage);
if (entry.error) {
console.error(entry.error.stack);
}
} else if (entry.level === 1 /* WARN */) {
console.warn(consoleMessage);
} else {
console.log(consoleMessage);
}
}
}
error(message, errorOrContext, context) {
const isError = errorOrContext instanceof Error;
this.writeLog({
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
level: 0 /* ERROR */,
message,
context: isError ? context : errorOrContext,
error: isError ? errorOrContext : void 0
});
}
warn(message, errorOrContext) {
const isError = errorOrContext instanceof Error;
this.writeLog({
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
level: 1 /* WARN */,
message,
context: isError ? void 0 : errorOrContext,
error: isError ? errorOrContext : void 0
});
}
info(message, context) {
this.writeLog({
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
level: 2 /* INFO */,
message,
context
});
}
debug(message, context) {
this.writeLog({
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
level: 3 /* DEBUG */,
message,
context
});
}
}
const logger = Logger.getInstance();
export {
LogLevel,
Logger,
logger
};
//# sourceMappingURL=logger.js.map