Files
2026-09-04 14:58:42 +08:00

129 lines
3.5 KiBLFS
JavaScript

#!/usr/bin/env node
import { appendFileSync, existsSync, mkdirSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Log file is in the skill root directory, not in scripts/
const SKILL_ROOT = join(__dirname, '..');
const LOG_FILE = join(SKILL_ROOT, 'action-log.jsonl');
/**
* Ensure the log file directory exists
*/
function ensureLogDirectory() {
const logDir = dirname(LOG_FILE);
if (!existsSync(logDir)) {
mkdirSync(logDir, { recursive: true });
}
}
/**
* Log an action to the log file
* @param {string} action - The action type (e.g., 'search', 'send', 'label')
* @param {Object} params - The parameters used for the action
* @param {Object} result - The result of the action
* @param {Object} metadata - Additional metadata (e.g., account, duration)
*/
export function logAction(action, params, result, metadata = {}) {
ensureLogDirectory();
const logEntry = {
timestamp: new Date().toISOString(),
action,
params: sanitizeParams(params),
result: sanitizeResult(result),
metadata: {
...metadata,
skill: 'gmail'
}
};
try {
// Append as JSON Lines format (one JSON object per line)
appendFileSync(LOG_FILE, JSON.stringify(logEntry) + '\n', 'utf8');
} catch (error) {
// Don't fail the main action if logging fails
console.error('Warning: Failed to write to action log:', error.message);
}
}
/**
* Sanitize parameters to remove sensitive data and limit size
*/
function sanitizeParams(params) {
const sanitized = { ...params };
// Remove sensitive fields
const sensitiveFields = ['password', 'token', 'access_token', 'refresh_token'];
sensitiveFields.forEach(field => {
if (sanitized[field]) {
sanitized[field] = '[REDACTED]';
}
});
// Truncate very long string values
Object.keys(sanitized).forEach(key => {
if (typeof sanitized[key] === 'string' && sanitized[key].length > 1000) {
sanitized[key] = sanitized[key].substring(0, 1000) + '... [truncated]';
}
});
return sanitized;
}
/**
* Sanitize result to limit size while keeping useful information
*/
function sanitizeResult(result) {
const sanitized = { ...result };
// Keep success status and counts
if (sanitized.success !== undefined) {
// Success is important
}
if (sanitized.count !== undefined || sanitized.total !== undefined) {
// Counts are useful
}
// For message/email results, keep summary info but limit details
if (sanitized.messages && Array.isArray(sanitized.messages)) {
sanitized.messageCount = sanitized.messages.length;
// Keep only essential fields from first few messages
sanitized.messageSummary = sanitized.messages.slice(0, 3).map(msg => ({
id: msg.id,
threadId: msg.threadId,
from: msg.from,
subject: msg.subject,
date: msg.date,
labels: msg.labels
}));
delete sanitized.messages;
}
// For label results
if (sanitized.labels && Array.isArray(sanitized.labels)) {
sanitized.labelCount = sanitized.labels.length;
sanitized.labelNames = sanitized.labels.map(l => l.name || l.id).slice(0, 10);
delete sanitized.labels;
}
// Truncate error messages
if (sanitized.error && sanitized.error.length > 500) {
sanitized.error = sanitized.error.substring(0, 500) + '... [truncated]';
}
return sanitized;
}
/**
* Get the log file path (useful for scripts that want to reference it)
*/
export function getLogFilePath() {
return LOG_FILE;
}