134 lines
3.6 KiBLFS
JavaScript
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;
|
|
}
|