Skip to main content
Glama
utils.ts5.87 kB
/** * Shared utilities for cleanup scripts */ import { AttioRecord, ResourceType } from './types.js'; /** * Extract a human-readable name from a record */ export function extractRecordName(record: AttioRecord, resourceType: ResourceType): string { // Try to get name from values first (new API structure) if (record.values?.name?.[0]?.value) { return record.values.name[0].value; } switch (resourceType) { case 'companies': return record.name || 'Unknown'; case 'people': // Try full name from values.name[0] first (new API structure) if (record.values?.name?.[0]?.full_name) { return record.values.name[0].full_name; } // Try first/last name from values.name[0] const nameObj = record.values?.name?.[0]; if (nameObj?.first_name && nameObj?.last_name) { return `${nameObj.first_name} ${nameObj.last_name}`; } if (nameObj?.first_name || nameObj?.last_name) { return nameObj.first_name || nameObj.last_name; } // Fallback to legacy structure const firstName = record.values?.first_name?.[0]?.value || record.first_name; const lastName = record.values?.last_name?.[0]?.value || record.last_name; if (firstName && lastName) { return `${firstName} ${lastName}`; } return firstName || lastName || record.name || 'Unknown'; case 'tasks': return record.content_plaintext || record.content || record.title || 'Unknown'; case 'lists': return record.name || record.title || 'Unknown'; case 'notes': return record.title || record.content || 'Unknown'; default: return 'Unknown'; } } /** * Extract the ID from a record (handling different ID structures) */ export function extractRecordId(record: AttioRecord, resourceType: ResourceType): string { // Handle different ID structures if (typeof record.id === 'string') { return record.id; } // Handle object-based IDs (new API structure) if (record.id && typeof record.id === 'object') { // Try record_id first (new API structure) if (record.id.record_id) { return record.id.record_id; } // Fallback to resource-specific ID fields switch (resourceType) { case 'companies': return record.id.company_id || record.id.id; case 'people': return record.id.person_id || record.id.id; case 'tasks': return record.id.task_id || record.id.id; case 'lists': return record.id.list_id || record.id.id; case 'notes': return record.id.note_id || record.id.id; case 'deals': return record.id.deal_id || record.id.id; default: return record.id.id || JSON.stringify(record.id); } } return 'unknown-id'; } /** * Check if a record was created by a specific API token * Different object types have different field structures: * - Tasks: created_by_actor.type and created_by_actor.id * - Companies/People: values.created_by[0].referenced_actor_type and referenced_actor_id */ export function isCreatedByApiToken(record: AttioRecord, apiToken: string, resourceType: ResourceType): boolean { switch (resourceType) { case 'tasks': return record.created_by_actor?.type === 'api-token' && record.created_by_actor?.id === apiToken; case 'companies': case 'people': case 'lists': case 'notes': // These objects store created_by in the values field const createdBy = record.values?.created_by?.[0]; return createdBy?.referenced_actor_type === 'api-token' && createdBy?.referenced_actor_id === apiToken; default: return false; } } /** * Check if a record name matches any of the given patterns */ export function matchesPatterns(name: string, patterns: string[]): boolean { if (!patterns.length) return true; return patterns.some(pattern => { // Simple wildcard matching if (pattern.includes('*')) { const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$', 'i'); return regex.test(name); } // Prefix matching return name.toLowerCase().includes(pattern.toLowerCase()); }); } /** * Format duration in human-readable format */ export function formatDuration(ms: number): string { if (ms < 1000) return `${ms}ms`; if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`; return `${(ms / 60000).toFixed(1)}m`; } /** * Create a delay for rate limiting */ export function delay(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Chunk array into smaller arrays */ export function chunk<T>(array: T[], size: number): T[][] { const chunks: T[][] = []; for (let i = 0; i < array.length; i += size) { chunks.push(array.slice(i, i + size)); } return chunks; } /** * Log with consistent formatting */ export function log(emoji: string, message: string, data?: any): void { const timestamp = new Date().toISOString(); if (data) { console.log(`${emoji} [${timestamp}] ${message}`, data); } else { console.log(`${emoji} [${timestamp}] ${message}`); } } /** * Log error with consistent formatting */ export function logError(message: string, error?: any): void { log('❌', `ERROR: ${message}`, error); } /** * Log success with consistent formatting */ export function logSuccess(message: string, data?: any): void { log('✅', message, data); } /** * Log info with consistent formatting */ export function logInfo(message: string, data?: any): void { log('ℹ️', message, data); } /** * Validate environment configuration */ export function validateEnvironment(): { valid: boolean; missing: string[] } { const required = ['ATTIO_API_KEY']; const missing = required.filter(key => !process.env[key]); return { valid: missing.length === 0, missing }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kesslerio/attio-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server