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

134 lines
3.6 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., 'list_events', 'create_event', 'update_event')
* @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: 'google-calendar'
}
};
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 event results, keep summary info but limit details
if (sanitized.events && Array.isArray(sanitized.events)) {
sanitized.eventCount = sanitized.events.length;
// Keep only essential fields from first few events
sanitized.eventSummary = sanitized.events.slice(0, 3).map(event => ({
id: event.id,
summary: event.summary,
start: event.start,
end: event.end,
status: event.status,
attendees: event.attendees ? event.attendees.length : 0
}));
delete sanitized.events;
}
// For single event results
if (sanitized.event) {
sanitized.eventSummary = {
id: sanitized.event.id,
summary: sanitized.event.summary,
start: sanitized.event.start,
end: sanitized.event.end,
status: sanitized.event.status
};
delete sanitized.event;
}
// 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;
}