@planescrm/sdk 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts","../src/errors.ts","../src/http.ts","../src/resources/base-resource.ts","../src/resources/opportunities.ts","../src/resources/customers.ts","../src/resources/properties.ts","../src/resources/calendar-events.ts","../src/resources/contracts.ts","../src/resources/jobs.ts","../src/resources/estimates.ts","../src/resources/invoices.ts","../src/resources/inventory.ts","../src/client.ts","../src/agent.ts","../src/webhooks.ts","../src/oauth.ts"],"sourcesContent":["/**\n * Configuration for the BlueprintClient.\n */\nexport interface BlueprintConfig {\n /** API base URL (defaults to https://api.planescrm.com/api/v1) */\n baseUrl?: string;\n /** OAuth access token (mutually exclusive with apiKey) */\n accessToken?: string;\n /** API key (mutually exclusive with accessToken) */\n apiKey?: string;\n /** Request timeout in milliseconds (default 30000) */\n timeout?: number;\n /** Automatically retry on 429 rate limit responses (default true) */\n retryOnRateLimit?: boolean;\n /** Maximum number of retries for rate-limited requests (default 3) */\n maxRetries?: number;\n}\n\nexport const DEFAULT_BASE_URL = 'https://api.planescrm.com/api/v1';\nexport const DEFAULT_TIMEOUT = 30_000;\nexport const DEFAULT_MAX_RETRIES = 3;\n","/**\n * Base error class for all Planes SDK errors.\n */\nexport class BlueprintError extends Error {\n public readonly status: number;\n public readonly code: string;\n\n constructor(message: string, status: number, code: string) {\n super(message);\n this.name = 'BlueprintError';\n this.status = status;\n this.code = code;\n }\n}\n\n/**\n * Thrown when authentication fails (401).\n */\nexport class AuthenticationError extends BlueprintError {\n constructor(message = 'Authentication failed') {\n super(message, 401, 'authentication_error');\n this.name = 'AuthenticationError';\n }\n}\n\n/**\n * Thrown when the user lacks permission (403).\n */\nexport class AuthorizationError extends BlueprintError {\n constructor(message = 'Insufficient permissions') {\n super(message, 403, 'authorization_error');\n this.name = 'AuthorizationError';\n }\n}\n\n/**\n * Thrown when a resource is not found (404).\n */\nexport class NotFoundError extends BlueprintError {\n constructor(message = 'Resource not found') {\n super(message, 404, 'not_found');\n this.name = 'NotFoundError';\n }\n}\n\n/** Field-level validation error detail. */\nexport interface FieldError {\n field: string;\n message: string;\n code?: string;\n}\n\n/**\n * Thrown for validation failures (400) with field-level error details.\n */\nexport class ValidationError extends BlueprintError {\n public readonly fieldErrors: FieldError[];\n\n constructor(message = 'Validation failed', fieldErrors: FieldError[] = []) {\n super(message, 400, 'validation_error');\n this.name = 'ValidationError';\n this.fieldErrors = fieldErrors;\n }\n}\n\n/**\n * Thrown when rate limited (429). Includes retryAfter in seconds.\n */\nexport class RateLimitError extends BlueprintError {\n public readonly retryAfter: number;\n\n constructor(retryAfter = 1, message = 'Rate limit exceeded') {\n super(message, 429, 'rate_limit_error');\n this.name = 'RateLimitError';\n this.retryAfter = retryAfter;\n }\n}\n\n/**\n * Thrown for server errors (500+).\n */\nexport class ServerError extends BlueprintError {\n constructor(message = 'Internal server error') {\n super(message, 500, 'server_error');\n this.name = 'ServerError';\n }\n}\n\n/**\n * Thrown when webhook signature verification fails.\n */\nexport class WebhookVerificationError extends BlueprintError {\n constructor(message = 'Webhook signature verification failed') {\n super(message, 400, 'webhook_verification_error');\n this.name = 'WebhookVerificationError';\n }\n}\n\n/**\n * Thrown for OAuth flow errors.\n */\nexport class OAuthError extends BlueprintError {\n public readonly errorCode: string;\n\n constructor(errorCode: string, message = 'OAuth error') {\n super(message, 400, 'oauth_error');\n this.name = 'OAuthError';\n this.errorCode = errorCode;\n }\n}\n","import {\n BlueprintConfig,\n DEFAULT_BASE_URL,\n DEFAULT_TIMEOUT,\n DEFAULT_MAX_RETRIES,\n} from './config';\nimport {\n BlueprintError,\n AuthenticationError,\n AuthorizationError,\n NotFoundError,\n ValidationError,\n RateLimitError,\n ServerError,\n} from './errors';\nimport type { FieldError } from './errors';\n\nexport interface HttpRequestOptions {\n method: string;\n path: string;\n body?: unknown;\n params?: Record<string, unknown>;\n headers?: Record<string, string>;\n}\n\n/**\n * Internal HTTP client wrapper using native fetch.\n * Handles authentication, rate limit retries, and error mapping.\n */\nexport class HttpClient {\n private readonly baseUrl: string;\n private readonly accessToken: string | undefined;\n private readonly apiKey: string | undefined;\n private readonly timeout: number;\n private readonly retryOnRateLimit: boolean;\n private readonly maxRetries: number;\n\n constructor(config: BlueprintConfig) {\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n this.accessToken = config.accessToken;\n this.apiKey = config.apiKey;\n this.timeout = config.timeout ?? DEFAULT_TIMEOUT;\n this.retryOnRateLimit = config.retryOnRateLimit !== false;\n this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n }\n\n async request<T>(options: HttpRequestOptions): Promise<T> {\n const url = this.buildUrl(options.path, options.params);\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.getAuthHeaders(),\n ...options.headers,\n };\n\n const fetchOptions: RequestInit = {\n method: options.method,\n headers,\n signal: AbortSignal.timeout(this.timeout),\n };\n\n if (options.body !== undefined) {\n fetchOptions.body = JSON.stringify(options.body);\n }\n\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n const response = await fetch(url, fetchOptions);\n\n if (response.ok) {\n // 204 No Content\n if (response.status === 204) {\n return undefined as T;\n }\n return (await response.json()) as T;\n }\n\n if (response.status === 429 && this.retryOnRateLimit && attempt < this.maxRetries) {\n const retryAfter = this.parseRetryAfter(response);\n await this.sleep(retryAfter * 1000);\n continue;\n }\n\n lastError = await this.mapError(response);\n\n // Only retry on 429 when enabled; all other errors fail immediately\n if (response.status !== 429 || !this.retryOnRateLimit) {\n throw lastError;\n }\n }\n\n // All retries exhausted for 429\n throw lastError ?? new RateLimitError();\n }\n\n getAuthHeaders(): Record<string, string> {\n if (this.accessToken) {\n return { Authorization: `Bearer ${this.accessToken}` };\n }\n if (this.apiKey) {\n return { 'X-API-Key': this.apiKey };\n }\n return {};\n }\n\n private buildUrl(path: string, params?: Record<string, unknown>): string {\n const url = new URL(`${this.baseUrl}${path}`);\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n return url.toString();\n }\n\n private parseRetryAfter(response: Response): number {\n const header = response.headers.get('Retry-After');\n if (header) {\n const seconds = Number(header);\n if (!Number.isNaN(seconds) && seconds > 0) {\n return seconds;\n }\n }\n return 1; // Default 1 second\n }\n\n private async mapError(response: Response): Promise<BlueprintError> {\n let body: Record<string, unknown> = {};\n try {\n body = (await response.json()) as Record<string, unknown>;\n } catch {\n // Ignore JSON parse errors\n }\n\n const message = (body['message'] as string) ?? response.statusText ?? 'Request failed';\n\n switch (response.status) {\n case 400: {\n const fieldErrors = (body['errors'] as FieldError[] | undefined) ?? [];\n return new ValidationError(message, fieldErrors);\n }\n case 401:\n return new AuthenticationError(message);\n case 403:\n return new AuthorizationError(message);\n case 404:\n return new NotFoundError(message);\n case 429: {\n const retryAfter = this.parseRetryAfter(response);\n return new RateLimitError(retryAfter, message);\n }\n default:\n if (response.status >= 500) {\n return new ServerError(message);\n }\n return new BlueprintError(message, response.status, 'unknown_error');\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import { HttpClient } from '../http';\nimport type { PaginatedResponse, ListParams } from '../types';\n\n/**\n * Abstract base resource with standard CRUD methods.\n * Generic parameters:\n * - TEntity: the entity response type\n * - TCreate: the create request body type\n * - TUpdate: the update request body type\n */\nexport abstract class BaseResource<\n TEntity = Record<string, unknown>,\n TCreate = Record<string, unknown>,\n TUpdate = Record<string, unknown>,\n> {\n constructor(\n protected readonly http: HttpClient,\n protected readonly basePath: string,\n ) {}\n\n /**\n * List entities with optional filters and pagination.\n */\n async list(params?: ListParams): Promise<PaginatedResponse<TEntity>> {\n return this.http.request<PaginatedResponse<TEntity>>({\n method: 'GET',\n path: this.basePath,\n params: params as Record<string, unknown>,\n });\n }\n\n /**\n * Get a single entity by ID.\n */\n async get(id: string): Promise<TEntity> {\n return this.http.request<TEntity>({\n method: 'GET',\n path: `${this.basePath}/${id}`,\n });\n }\n\n /**\n * Create a new entity.\n */\n async create(data: TCreate): Promise<TEntity> {\n return this.http.request<TEntity>({\n method: 'POST',\n path: this.basePath,\n body: data,\n });\n }\n\n /**\n * Update an existing entity.\n */\n async update(id: string, data: TUpdate): Promise<TEntity> {\n return this.http.request<TEntity>({\n method: 'PATCH',\n path: `${this.basePath}/${id}`,\n body: data,\n });\n }\n\n /**\n * Delete an entity by ID.\n */\n async delete(id: string): Promise<void> {\n await this.http.request<void>({\n method: 'DELETE',\n path: `${this.basePath}/${id}`,\n });\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Opportunity resource — CRUD operations on /opportunities. */\nexport class OpportunityResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/opportunities');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Customer resource — CRUD operations on /customers. */\nexport class CustomerResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/customers');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Property resource — CRUD operations on /properties. */\nexport class PropertyResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/properties');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Calendar event resource — CRUD operations on /calendar-events. */\nexport class CalendarEventResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/calendar-events');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Contract resource — CRUD operations on /contracts. */\nexport class ContractResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/contracts');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Job resource — CRUD operations on /jobs. */\nexport class JobResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/jobs');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Estimate resource — CRUD operations on /estimates. */\nexport class EstimateResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/estimates');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Invoice resource — CRUD operations on /invoices. */\nexport class InvoiceResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/invoices');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Inventory resource — CRUD operations on /inventory/items. */\nexport class InventoryResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/inventory/items');\n }\n}\n","import type { BlueprintConfig } from './config';\nimport { HttpClient } from './http';\nimport { OpportunityResource } from './resources/opportunities';\nimport { CustomerResource } from './resources/customers';\nimport { PropertyResource } from './resources/properties';\nimport { CalendarEventResource } from './resources/calendar-events';\nimport { ContractResource } from './resources/contracts';\nimport { JobResource } from './resources/jobs';\nimport { EstimateResource } from './resources/estimates';\nimport { InvoiceResource } from './resources/invoices';\nimport { InventoryResource } from './resources/inventory';\n\n/**\n * BlueprintClient — typed API client for the Planes CRM platform.\n *\n * @example\n * ```ts\n * const client = new BlueprintClient({ accessToken: 'tok_...' });\n * const opps = await client.opportunities.list({ page: 1, limit: 20 });\n * ```\n */\nexport class BlueprintClient {\n private readonly http: HttpClient;\n private _opportunities?: OpportunityResource;\n private _customers?: CustomerResource;\n private _properties?: PropertyResource;\n private _calendarEvents?: CalendarEventResource;\n private _contracts?: ContractResource;\n private _jobs?: JobResource;\n private _estimates?: EstimateResource;\n private _invoices?: InvoiceResource;\n private _inventory?: InventoryResource;\n\n constructor(config: BlueprintConfig) {\n if (!config.accessToken && !config.apiKey) {\n throw new Error('BlueprintClient requires either accessToken or apiKey');\n }\n if (config.accessToken && config.apiKey) {\n throw new Error('BlueprintClient requires either accessToken or apiKey, not both');\n }\n this.http = new HttpClient(config);\n }\n\n get opportunities(): OpportunityResource {\n if (!this._opportunities) {\n this._opportunities = new OpportunityResource(this.http);\n }\n return this._opportunities;\n }\n\n get customers(): CustomerResource {\n if (!this._customers) {\n this._customers = new CustomerResource(this.http);\n }\n return this._customers;\n }\n\n get properties(): PropertyResource {\n if (!this._properties) {\n this._properties = new PropertyResource(this.http);\n }\n return this._properties;\n }\n\n get calendarEvents(): CalendarEventResource {\n if (!this._calendarEvents) {\n this._calendarEvents = new CalendarEventResource(this.http);\n }\n return this._calendarEvents;\n }\n\n get contracts(): ContractResource {\n if (!this._contracts) {\n this._contracts = new ContractResource(this.http);\n }\n return this._contracts;\n }\n\n get jobs(): JobResource {\n if (!this._jobs) {\n this._jobs = new JobResource(this.http);\n }\n return this._jobs;\n }\n\n get estimates(): EstimateResource {\n if (!this._estimates) {\n this._estimates = new EstimateResource(this.http);\n }\n return this._estimates;\n }\n\n get invoices(): InvoiceResource {\n if (!this._invoices) {\n this._invoices = new InvoiceResource(this.http);\n }\n return this._invoices;\n }\n\n get inventory(): InventoryResource {\n if (!this._inventory) {\n this._inventory = new InventoryResource(this.http);\n }\n return this._inventory;\n }\n}\n","import * as http from 'node:http';\nimport type { ActionHandler, ActionContext, ActionResult } from './types/agent-types';\n\n/**\n * Options for Blueprint.register().\n */\nexport interface RegisterOptions {\n /** Port to listen on (default 3000). */\n port?: number;\n /** HTTP path to listen on (default '/actions'). */\n path?: string;\n /** Whether to verify incoming request signatures (default true). */\n verifySignature?: boolean;\n /** Error callback for unhandled errors. */\n onError?: (error: Error) => void;\n}\n\n/**\n * Blueprint agent registration namespace.\n * Provides a convenient way to register action handlers as an HTTP server.\n */\nexport const Blueprint = {\n /**\n * Register action handlers and start an HTTP server to receive action requests.\n *\n * @example\n * ```ts\n * Blueprint.register([\n * {\n * type: 'send_email',\n * async execute(ctx) {\n * // ... send email logic\n * return { status: 'completed', output: { sent: true } };\n * },\n * },\n * ], { port: 3000 });\n * ```\n */\n register(handlers: ActionHandler[], options?: RegisterOptions): http.Server {\n const port = options?.port ?? 3000;\n const basePath = options?.path ?? '/actions';\n const onError = options?.onError;\n\n // Build handler map for O(1) lookup\n const handlerMap = new Map<string, ActionHandler>();\n for (const handler of handlers) {\n handlerMap.set(handler.type, handler);\n }\n\n const server = http.createServer(async (req: http.IncomingMessage, res: http.ServerResponse) => {\n // Only handle POST to the configured path\n if (req.method !== 'POST' || req.url !== basePath) {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not found' }));\n return;\n }\n\n try {\n const body = await readBody(req);\n\n let context: ActionContext;\n try {\n context = JSON.parse(body) as ActionContext;\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Malformed request body' }));\n return;\n }\n\n // Validate minimum required fields\n if (!context.action?.type) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Missing action.type in request body' }));\n return;\n }\n\n const handler = handlerMap.get(context.action.type);\n if (!handler) {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n error: `No handler registered for action type: ${context.action.type}`,\n }),\n );\n return;\n }\n\n let result: ActionResult;\n try {\n result = await handler.execute(context);\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : 'Unknown handler error';\n if (onError && err instanceof Error) {\n onError(err);\n }\n result = {\n status: 'failed',\n error: errorMessage,\n retryable: false,\n };\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(result));\n } catch (err) {\n if (onError && err instanceof Error) {\n onError(err);\n }\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Internal server error' }));\n }\n });\n\n server.listen(port);\n\n // Graceful shutdown\n const shutdown = () => {\n server.close();\n };\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n return server;\n },\n};\n\nfunction readBody(req: http.IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks).toString()));\n req.on('error', reject);\n });\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto';\nimport { WebhookVerificationError } from './errors';\n\n/**\n * Typed webhook event payload from Planes.\n */\nexport interface WebhookEvent<T = unknown> {\n /** Event ID (e.g. evt_abc123). */\n id: string;\n /** Event type (e.g. opportunity.created). */\n type: string;\n /** Tenant ID that owns this event. */\n tenant_id: string;\n /** ISO 8601 timestamp of when the event occurred. */\n timestamp: string;\n /** Entity snapshot at the time of the event. */\n data: T;\n /** Metadata about the event trigger. */\n metadata: {\n correlation_id: string;\n triggered_by: 'workflow' | 'api' | 'user';\n };\n}\n\n/**\n * Verify an HMAC-SHA256 webhook signature using timing-safe comparison.\n *\n * The signature header format is: `sha256=<hex-digest>`\n *\n * @param payload - The raw request body (string or Buffer)\n * @param signature - The X-Blueprint-Signature header value\n * @param secret - The webhook signing secret\n * @returns true if the signature is valid, false otherwise\n */\nexport function verifyWebhookSignature(\n payload: string | Buffer,\n signature: string,\n secret: string,\n): boolean {\n if (!signature || !secret || !payload) {\n return false;\n }\n\n // Expect format: sha256=<hex>\n const prefix = 'sha256=';\n if (!signature.startsWith(prefix)) {\n return false;\n }\n\n const receivedHex = signature.slice(prefix.length);\n if (!receivedHex) {\n return false;\n }\n\n const expectedHmac = createHmac('sha256', secret)\n .update(payload)\n .digest('hex');\n\n // Convert to buffers for timing-safe comparison\n const receivedBuf = Buffer.from(receivedHex, 'hex');\n const expectedBuf = Buffer.from(expectedHmac, 'hex');\n\n if (receivedBuf.length !== expectedBuf.length) {\n return false;\n }\n\n return timingSafeEqual(receivedBuf, expectedBuf);\n}\n\n/**\n * Parse and verify a webhook event payload.\n *\n * @param body - The raw request body as a string\n * @param signature - The X-Blueprint-Signature header value\n * @param secret - The webhook signing secret\n * @returns The parsed and typed webhook event\n * @throws WebhookVerificationError if signature is invalid\n */\nexport function parseWebhookEvent<T = unknown>(\n body: string,\n signature: string,\n secret: string,\n): WebhookEvent<T> {\n if (!verifyWebhookSignature(body, signature, secret)) {\n throw new WebhookVerificationError();\n }\n\n return JSON.parse(body) as WebhookEvent<T>;\n}\n","import { randomBytes } from 'node:crypto';\nimport { OAuthError } from './errors';\n\n/**\n * Options for building an OAuth authorization URL.\n */\nexport interface AuthorizeOptions {\n /** Planes API base URL (e.g. https://api.planescrm.com/api/v1). */\n baseUrl: string;\n /** OAuth client ID. */\n clientId: string;\n /** Redirect URI after authorization. */\n redirectUri: string;\n /** Requested OAuth scopes. */\n scopes: string[];\n /** CSRF state parameter (auto-generated if not provided). */\n state?: string;\n}\n\n/**\n * Options for exchanging an authorization code for tokens.\n */\nexport interface ExchangeCodeOptions {\n /** Planes API base URL. */\n baseUrl: string;\n /** OAuth client ID. */\n clientId: string;\n /** OAuth client secret. */\n clientSecret: string;\n /** Authorization code from the callback. */\n code: string;\n /** Redirect URI (must match the one used in authorization). */\n redirectUri: string;\n}\n\n/**\n * Options for refreshing an access token.\n */\nexport interface RefreshOptions {\n /** Planes API base URL. */\n baseUrl: string;\n /** OAuth client ID. */\n clientId: string;\n /** OAuth client secret. */\n clientSecret: string;\n /** Refresh token. */\n refreshToken: string;\n}\n\n/**\n * OAuth token response.\n */\nexport interface TokenResponse {\n access_token: string;\n refresh_token: string;\n expires_in: number;\n token_type: string;\n scope: string;\n}\n\n/**\n * Build an OAuth authorization URL for the Planes platform.\n *\n * @param options - Authorization options\n * @returns The full authorization URL to redirect the user to\n */\nexport function buildAuthorizeUrl(options: AuthorizeOptions): string {\n const state = options.state ?? randomBytes(16).toString('hex');\n const baseUrl = options.baseUrl.replace(/\\/+$/, '');\n\n const url = new URL(`${baseUrl}/oauth/authorize`);\n url.searchParams.set('client_id', options.clientId);\n url.searchParams.set('redirect_uri', options.redirectUri);\n url.searchParams.set('scope', options.scopes.join(' '));\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('state', state);\n\n return url.toString();\n}\n\n/**\n * Exchange an authorization code for access and refresh tokens.\n *\n * @param options - Exchange options\n * @returns Token response with access_token, refresh_token, etc.\n * @throws OAuthError on failure\n */\nexport async function exchangeCode(options: ExchangeCodeOptions): Promise<TokenResponse> {\n const baseUrl = options.baseUrl.replace(/\\/+$/, '');\n const url = `${baseUrl}/oauth/token`;\n\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: options.clientId,\n client_secret: options.clientSecret,\n code: options.code,\n redirect_uri: options.redirectUri,\n });\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({})) as Record<string, unknown>;\n const errorCode = (errorBody['error'] as string) ?? 'unknown_error';\n const errorDescription = (errorBody['error_description'] as string) ?? 'Token exchange failed';\n throw new OAuthError(errorCode, errorDescription);\n }\n\n return (await response.json()) as TokenResponse;\n}\n\n/**\n * Refresh an access token using a refresh token.\n *\n * @param options - Refresh options\n * @returns New token response\n * @throws OAuthError on failure\n */\nexport async function refreshAccessToken(options: RefreshOptions): Promise<TokenResponse> {\n const baseUrl = options.baseUrl.replace(/\\/+$/, '');\n const url = `${baseUrl}/oauth/token`;\n\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: options.clientId,\n client_secret: options.clientSecret,\n refresh_token: options.refreshToken,\n });\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({})) as Record<string, unknown>;\n const errorCode = (errorBody['error'] as string) ?? 'unknown_error';\n const errorDescription = (errorBody['error_description'] as string) ?? 'Token refresh failed';\n throw new OAuthError(errorCode, errorDescription);\n }\n\n return (await response.json()) as TokenResponse;\n}\n"],"mappings":"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","names":["http","http","http","http","http","http","http","http","http","http"]}
1
+ {"version":3,"sources":["../src/config.ts","../src/errors.ts","../src/oauth.ts","../src/http.ts","../src/resources/base-resource.ts","../src/resources/opportunities.ts","../src/resources/customers.ts","../src/resources/properties.ts","../src/resources/calendar-events.ts","../src/resources/contracts.ts","../src/resources/jobs.ts","../src/resources/estimates.ts","../src/resources/invoices.ts","../src/resources/inventory.ts","../src/resources/contacts.ts","../src/resources/activities.ts","../src/resources/tags.ts","../src/webhooks.ts","../src/webhook-router.ts","../src/client.ts","../src/agent.ts"],"sourcesContent":["/**\n * Tokens managed by an OAuth-credential client, persisted via {@link TokenStorage}.\n */\nexport interface StoredTokens {\n /** Current OAuth access token. */\n accessToken: string;\n /**\n * Refresh token used to obtain a new access token. `null` for tokens issued\n * via the client_credentials grant, which have no refresh token — the\n * client re-authenticates with clientId/clientSecret instead.\n */\n refreshToken: string | null;\n /** Access-token expiry as epoch milliseconds. */\n expiresAt: number;\n}\n\n/**\n * Pluggable persistence for OAuth-managed tokens. Implement this to back the\n * client with your own store (encrypted disk, database, secrets manager, ...).\n * The default is {@link MemoryTokenStorage}.\n */\nexport interface TokenStorage {\n /** Load the currently stored tokens, or `null` if none are stored. */\n getTokens(): Promise<StoredTokens | null>;\n /** Persist the given tokens (called after every refresh). */\n setTokens(tokens: StoredTokens): Promise<void>;\n}\n\n/**\n * Default in-memory {@link TokenStorage}. Tokens live only for the process\n * lifetime — provide your own implementation to persist across restarts.\n */\nexport class MemoryTokenStorage implements TokenStorage {\n private tokens: StoredTokens | null;\n\n constructor(seed?: StoredTokens | null) {\n this.tokens = seed ?? null;\n }\n\n async getTokens(): Promise<StoredTokens | null> {\n return this.tokens;\n }\n\n async setTokens(tokens: StoredTokens): Promise<void> {\n this.tokens = tokens;\n }\n}\n\n/**\n * Configuration for the PlanesClient.\n *\n * Exactly one authentication mode must be supplied:\n * - `accessToken` — a pre-obtained OAuth access token (not auto-refreshed)\n * - `apiKey` — a `pk_live_` API key\n * - `clientId` + `clientSecret` — OAuth-managed: the client obtains/refreshes\n * access tokens automatically using `tokenStorage`\n */\nexport interface PlanesConfig {\n /** API base URL (defaults to https://api.planescrm.com/api/v1) */\n baseUrl?: string;\n /** OAuth access token (mutually exclusive with apiKey / clientId+clientSecret) */\n accessToken?: string;\n /** API key (mutually exclusive with accessToken / clientId+clientSecret) */\n apiKey?: string;\n /** OAuth client ID — combined with `clientSecret`, enables the OAuth-managed path */\n clientId?: string;\n /** OAuth client secret — combined with `clientId`, enables the OAuth-managed path */\n clientSecret?: string;\n /**\n * Scopes to request when the OAuth-managed path falls back to the\n * client_credentials grant (no stored refresh token).\n */\n scopes?: string[];\n /**\n * Token store for the OAuth-managed path. Seed it with tokens obtained from\n * an authorization-code exchange. Defaults to {@link MemoryTokenStorage}.\n */\n tokenStorage?: TokenStorage;\n /**\n * Refresh access tokens proactively when they are within this many\n * milliseconds of expiry (OAuth-managed path only). Default 60000 (60s).\n */\n tokenRefreshSkewMs?: number;\n /** Request timeout in milliseconds (default 30000) */\n timeout?: number;\n /** Automatically retry on 429 rate limit responses (default true) */\n retryOnRateLimit?: boolean;\n /** Maximum number of retries for rate-limited requests (default 3) */\n maxRetries?: number;\n}\n\nexport const DEFAULT_BASE_URL = 'https://api.planescrm.com/api/v1';\nexport const DEFAULT_TIMEOUT = 30_000;\nexport const DEFAULT_MAX_RETRIES = 3;\nexport const DEFAULT_TOKEN_REFRESH_SKEW_MS = 60_000;\n","/**\n * Base error class for all Planes SDK errors.\n */\nexport class PlanesError extends Error {\n public readonly status: number;\n public readonly code: string;\n\n constructor(message: string, status: number, code: string) {\n super(message);\n this.name = 'PlanesError';\n this.status = status;\n this.code = code;\n }\n}\n\n/**\n * Thrown when authentication fails (401).\n */\nexport class AuthenticationError extends PlanesError {\n constructor(message = 'Authentication failed') {\n super(message, 401, 'authentication_error');\n this.name = 'AuthenticationError';\n }\n}\n\n/**\n * Thrown when the user lacks permission (403).\n */\nexport class AuthorizationError extends PlanesError {\n constructor(message = 'Insufficient permissions') {\n super(message, 403, 'authorization_error');\n this.name = 'AuthorizationError';\n }\n}\n\n/**\n * Thrown when a resource is not found (404).\n */\nexport class NotFoundError extends PlanesError {\n constructor(message = 'Resource not found') {\n super(message, 404, 'not_found');\n this.name = 'NotFoundError';\n }\n}\n\n/** Field-level validation error detail. */\nexport interface FieldError {\n field: string;\n message: string;\n code?: string;\n}\n\n/**\n * Thrown for validation failures (400) with field-level error details.\n */\nexport class ValidationError extends PlanesError {\n public readonly fieldErrors: FieldError[];\n\n constructor(message = 'Validation failed', fieldErrors: FieldError[] = []) {\n super(message, 400, 'validation_error');\n this.name = 'ValidationError';\n this.fieldErrors = fieldErrors;\n }\n}\n\n/**\n * Thrown when rate limited (429). Includes retryAfter in seconds.\n */\nexport class RateLimitError extends PlanesError {\n public readonly retryAfter: number;\n\n constructor(retryAfter = 1, message = 'Rate limit exceeded') {\n super(message, 429, 'rate_limit_error');\n this.name = 'RateLimitError';\n this.retryAfter = retryAfter;\n }\n}\n\n/**\n * Thrown for server errors (500+).\n */\nexport class ServerError extends PlanesError {\n constructor(message = 'Internal server error') {\n super(message, 500, 'server_error');\n this.name = 'ServerError';\n }\n}\n\n/**\n * Thrown when webhook signature verification fails.\n */\nexport class WebhookVerificationError extends PlanesError {\n constructor(message = 'Webhook signature verification failed') {\n super(message, 400, 'webhook_verification_error');\n this.name = 'WebhookVerificationError';\n }\n}\n\n/**\n * Thrown for OAuth flow errors.\n */\nexport class OAuthError extends PlanesError {\n public readonly errorCode: string;\n\n constructor(errorCode: string, message = 'OAuth error') {\n super(message, 400, 'oauth_error');\n this.name = 'OAuthError';\n this.errorCode = errorCode;\n }\n}\n","import { randomBytes } from 'node:crypto';\nimport { OAuthError } from './errors';\n\n/**\n * Options for building an OAuth authorization URL.\n */\nexport interface AuthorizeOptions {\n /** Planes API base URL (e.g. https://api.planescrm.com/api/v1). */\n baseUrl: string;\n /** OAuth client ID. */\n clientId: string;\n /** Redirect URI after authorization. */\n redirectUri: string;\n /** Requested OAuth scopes. */\n scopes: string[];\n /** CSRF state parameter (auto-generated if not provided). */\n state?: string;\n}\n\n/**\n * Options for exchanging an authorization code for tokens.\n */\nexport interface ExchangeCodeOptions {\n /** Planes API base URL. */\n baseUrl: string;\n /** OAuth client ID. */\n clientId: string;\n /** OAuth client secret. */\n clientSecret: string;\n /** Authorization code from the callback. */\n code: string;\n /** Redirect URI (must match the one used in authorization). */\n redirectUri: string;\n}\n\n/**\n * Options for refreshing an access token.\n */\nexport interface RefreshOptions {\n /** Planes API base URL. */\n baseUrl: string;\n /** OAuth client ID. */\n clientId: string;\n /** OAuth client secret. */\n clientSecret: string;\n /** Refresh token. */\n refreshToken: string;\n}\n\n/**\n * Options for the client_credentials grant (M2M auth — no user/tenant context).\n */\nexport interface ClientCredentialsOptions {\n /** Planes API base URL. */\n baseUrl: string;\n /** OAuth client ID. */\n clientId: string;\n /** OAuth client secret. */\n clientSecret: string;\n /** Requested OAuth scopes. */\n scopes: string[];\n}\n\n/**\n * OAuth token response. `refresh_token` is absent for the client_credentials\n * grant (there is no user session to refresh on behalf of).\n */\nexport interface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in: number;\n token_type: string;\n scope: string;\n}\n\n/**\n * Build an OAuth authorization URL for the Planes platform.\n *\n * @param options - Authorization options\n * @returns The full authorization URL to redirect the user to\n */\nexport function buildAuthorizeUrl(options: AuthorizeOptions): string {\n const state = options.state ?? randomBytes(16).toString('hex');\n const baseUrl = options.baseUrl.replace(/\\/+$/, '');\n\n const url = new URL(`${baseUrl}/oauth/authorize`);\n url.searchParams.set('client_id', options.clientId);\n url.searchParams.set('redirect_uri', options.redirectUri);\n url.searchParams.set('scope', options.scopes.join(' '));\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('state', state);\n\n return url.toString();\n}\n\n/**\n * Exchange an authorization code for access and refresh tokens.\n *\n * @param options - Exchange options\n * @returns Token response with access_token, refresh_token, etc.\n * @throws OAuthError on failure\n */\nexport async function exchangeCode(options: ExchangeCodeOptions): Promise<TokenResponse> {\n const baseUrl = options.baseUrl.replace(/\\/+$/, '');\n const url = `${baseUrl}/oauth/token`;\n\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: options.clientId,\n client_secret: options.clientSecret,\n code: options.code,\n redirect_uri: options.redirectUri,\n });\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({})) as Record<string, unknown>;\n const errorCode = (errorBody['error'] as string) ?? 'unknown_error';\n const errorDescription = (errorBody['error_description'] as string) ?? 'Token exchange failed';\n throw new OAuthError(errorCode, errorDescription);\n }\n\n return (await response.json()) as TokenResponse;\n}\n\n/**\n * Refresh an access token using a refresh token.\n *\n * @param options - Refresh options\n * @returns New token response\n * @throws OAuthError on failure\n */\nexport async function refreshAccessToken(options: RefreshOptions): Promise<TokenResponse> {\n const baseUrl = options.baseUrl.replace(/\\/+$/, '');\n const url = `${baseUrl}/oauth/token`;\n\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: options.clientId,\n client_secret: options.clientSecret,\n refresh_token: options.refreshToken,\n });\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => ({})) as Record<string, unknown>;\n const errorCode = (errorBody['error'] as string) ?? 'unknown_error';\n const errorDescription = (errorBody['error_description'] as string) ?? 'Token refresh failed';\n throw new OAuthError(errorCode, errorDescription);\n }\n\n return (await response.json()) as TokenResponse;\n}\n\n/**\n * Obtain an access token via the client_credentials grant — the M2M auth\n * pattern for connectors and external services with no user/tenant context.\n *\n * @param options - Client credentials options\n * @returns Token response (no refresh_token)\n * @throws OAuthError on failure\n */\nexport async function clientCredentialsGrant(\n options: ClientCredentialsOptions,\n): Promise<TokenResponse> {\n const baseUrl = options.baseUrl.replace(/\\/+$/, '');\n const url = `${baseUrl}/oauth/token`;\n\n const body = new URLSearchParams({\n grant_type: 'client_credentials',\n client_id: options.clientId,\n client_secret: options.clientSecret,\n scope: options.scopes.join(' '),\n });\n\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: body.toString(),\n });\n\n if (!response.ok) {\n const errorBody = (await response.json().catch(() => ({}))) as Record<string, unknown>;\n const errorCode = (errorBody['error'] as string) ?? 'unknown_error';\n const errorDescription =\n (errorBody['error_description'] as string) ?? 'Client credentials grant failed';\n throw new OAuthError(errorCode, errorDescription);\n }\n\n return (await response.json()) as TokenResponse;\n}\n","import {\n PlanesConfig,\n DEFAULT_BASE_URL,\n DEFAULT_TIMEOUT,\n DEFAULT_MAX_RETRIES,\n DEFAULT_TOKEN_REFRESH_SKEW_MS,\n MemoryTokenStorage,\n} from './config';\nimport type { StoredTokens, TokenStorage } from './config';\nimport {\n PlanesError,\n AuthenticationError,\n AuthorizationError,\n NotFoundError,\n ValidationError,\n RateLimitError,\n ServerError,\n} from './errors';\nimport type { FieldError } from './errors';\nimport { clientCredentialsGrant, refreshAccessToken } from './oauth';\n\n/** OAuth-managed authentication context (clientId/clientSecret path). */\ninterface OAuthContext {\n clientId: string;\n clientSecret: string;\n tokenStorage: TokenStorage;\n /** Refresh proactively when within this many ms of expiry. */\n skewMs: number;\n /** Scopes to request via the client_credentials grant (M2M fallback). */\n scopes: string[] | undefined;\n}\n\nexport interface HttpRequestOptions {\n method: string;\n path: string;\n body?: unknown;\n params?: Record<string, unknown>;\n headers?: Record<string, string>;\n}\n\n/**\n * Internal HTTP client wrapper using native fetch.\n * Handles authentication, rate limit retries, and error mapping.\n */\nexport class HttpClient {\n private readonly baseUrl: string;\n private readonly accessToken: string | undefined;\n private readonly apiKey: string | undefined;\n private readonly timeout: number;\n private readonly retryOnRateLimit: boolean;\n private readonly maxRetries: number;\n private readonly oauth: OAuthContext | undefined;\n /** Single-flight guard so concurrent requests share one refresh. */\n private refreshInFlight: Promise<StoredTokens> | undefined;\n\n constructor(config: PlanesConfig) {\n this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, '');\n this.accessToken = config.accessToken;\n this.apiKey = config.apiKey;\n this.timeout = config.timeout ?? DEFAULT_TIMEOUT;\n this.retryOnRateLimit = config.retryOnRateLimit !== false;\n this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n\n if (config.clientId && config.clientSecret) {\n this.oauth = {\n clientId: config.clientId,\n clientSecret: config.clientSecret,\n tokenStorage: config.tokenStorage ?? new MemoryTokenStorage(),\n skewMs: config.tokenRefreshSkewMs ?? DEFAULT_TOKEN_REFRESH_SKEW_MS,\n scopes: config.scopes,\n };\n }\n }\n\n async request<T>(options: HttpRequestOptions): Promise<T> {\n const url = this.buildUrl(options.path, options.params);\n const bodyStr = options.body !== undefined ? JSON.stringify(options.body) : undefined;\n\n let lastError: Error | undefined;\n // The OAuth-managed 401 recovery is one refresh + one retry, independent of\n // the 429 rate-limit retry loop below.\n let didRefreshOn401 = false;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n // Resolve auth headers per-attempt: the OAuth path may proactively refresh\n // and the bearer token can change between attempts.\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...(await this.resolveAuthHeaders()),\n ...options.headers,\n };\n const fetchOptions: RequestInit = {\n method: options.method,\n headers,\n signal: AbortSignal.timeout(this.timeout),\n };\n if (bodyStr !== undefined) {\n fetchOptions.body = bodyStr;\n }\n\n const response = await fetch(url, fetchOptions);\n\n if (response.ok) {\n // 204 No Content\n if (response.status === 204) {\n return undefined as T;\n }\n return (await response.json()) as T;\n }\n\n if (response.status === 429 && this.retryOnRateLimit && attempt < this.maxRetries) {\n const retryAfter = this.parseRetryAfter(response);\n await this.sleep(retryAfter * 1000);\n continue;\n }\n\n // OAuth-managed path: on a 401, refresh the token once and retry the\n // request. This does not consume the 429 retry budget.\n if (response.status === 401 && this.oauth && !didRefreshOn401) {\n didRefreshOn401 = true;\n await this.refreshTokens();\n attempt--;\n continue;\n }\n\n lastError = await this.mapError(response);\n\n // Only retry on 429 when enabled; all other errors fail immediately\n if (response.status !== 429 || !this.retryOnRateLimit) {\n throw lastError;\n }\n }\n\n // All retries exhausted for 429\n throw lastError ?? new RateLimitError();\n }\n\n getAuthHeaders(): Record<string, string> {\n if (this.accessToken) {\n return { Authorization: `Bearer ${this.accessToken}` };\n }\n if (this.apiKey) {\n return { 'X-API-Key': this.apiKey };\n }\n return {};\n }\n\n /**\n * Resolve the Authorization header for the current request. For the static\n * accessToken/apiKey modes this is {@link getAuthHeaders}. For the\n * OAuth-managed path it reads the stored token, proactively refreshing when\n * the token is within the skew window of expiry.\n */\n private async resolveAuthHeaders(): Promise<Record<string, string>> {\n if (!this.oauth) {\n return this.getAuthHeaders();\n }\n\n const tokens = await this.oauth.tokenStorage.getTokens();\n if (!tokens) {\n // Nothing seeded — send no auth; the server 401 will drive a refresh\n // attempt (which will fail clearly if there is no refresh token).\n return {};\n }\n\n if (tokens.expiresAt - Date.now() <= this.oauth.skewMs) {\n const refreshed = await this.refreshTokens();\n return { Authorization: `Bearer ${refreshed.accessToken}` };\n }\n\n return { Authorization: `Bearer ${tokens.accessToken}` };\n }\n\n /**\n * Refresh the OAuth access token via `refreshAccessToken` and persist the new\n * tokens through the configured {@link TokenStorage}. Concurrent callers share\n * a single in-flight refresh.\n */\n private async refreshTokens(): Promise<StoredTokens> {\n if (!this.oauth) {\n throw new AuthenticationError('Token refresh requires the OAuth clientId/clientSecret path');\n }\n if (this.refreshInFlight) {\n return this.refreshInFlight;\n }\n\n const oauth = this.oauth;\n this.refreshInFlight = (async () => {\n const current = await oauth.tokenStorage.getTokens();\n\n if (!current?.refreshToken) {\n // No refresh token on file — fall back to the client_credentials\n // grant (M2M flow); there is no user session to refresh.\n const response = await clientCredentialsGrant({\n baseUrl: this.baseUrl,\n clientId: oauth.clientId,\n clientSecret: oauth.clientSecret,\n scopes: oauth.scopes ?? [],\n });\n\n const next: StoredTokens = {\n accessToken: response.access_token,\n refreshToken: response.refresh_token ?? null,\n expiresAt: Date.now() + response.expires_in * 1000,\n };\n await oauth.tokenStorage.setTokens(next);\n return next;\n }\n\n const response = await refreshAccessToken({\n baseUrl: this.baseUrl,\n clientId: oauth.clientId,\n clientSecret: oauth.clientSecret,\n refreshToken: current.refreshToken,\n });\n\n const next: StoredTokens = {\n accessToken: response.access_token,\n refreshToken: response.refresh_token || current.refreshToken,\n expiresAt: Date.now() + response.expires_in * 1000,\n };\n await oauth.tokenStorage.setTokens(next);\n return next;\n })();\n\n try {\n return await this.refreshInFlight;\n } finally {\n this.refreshInFlight = undefined;\n }\n }\n\n private buildUrl(path: string, params?: Record<string, unknown>): string {\n const url = new URL(`${this.baseUrl}${path}`);\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n url.searchParams.set(key, String(value));\n }\n }\n }\n return url.toString();\n }\n\n private parseRetryAfter(response: Response): number {\n const header = response.headers.get('Retry-After');\n if (header) {\n const seconds = Number(header);\n if (!Number.isNaN(seconds) && seconds > 0) {\n return seconds;\n }\n }\n return 1; // Default 1 second\n }\n\n private async mapError(response: Response): Promise<PlanesError> {\n let body: Record<string, unknown> = {};\n try {\n body = (await response.json()) as Record<string, unknown>;\n } catch {\n // Ignore JSON parse errors\n }\n\n const message = (body['message'] as string) ?? response.statusText ?? 'Request failed';\n\n switch (response.status) {\n case 400: {\n const fieldErrors = (body['errors'] as FieldError[] | undefined) ?? [];\n return new ValidationError(message, fieldErrors);\n }\n case 401:\n return new AuthenticationError(message);\n case 403:\n return new AuthorizationError(message);\n case 404:\n return new NotFoundError(message);\n case 429: {\n const retryAfter = this.parseRetryAfter(response);\n return new RateLimitError(retryAfter, message);\n }\n default:\n if (response.status >= 500) {\n return new ServerError(message);\n }\n return new PlanesError(message, response.status, 'unknown_error');\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import { HttpClient } from '../http';\nimport type { PaginatedResponse, ListParams } from '../types';\n\n/**\n * Abstract base resource with standard CRUD methods.\n * Generic parameters:\n * - TEntity: the entity response type\n * - TCreate: the create request body type\n * - TUpdate: the update request body type\n */\nexport abstract class BaseResource<\n TEntity = Record<string, unknown>,\n TCreate = Record<string, unknown>,\n TUpdate = Record<string, unknown>,\n> {\n constructor(\n protected readonly http: HttpClient,\n protected readonly basePath: string,\n ) {}\n\n /**\n * List entities with optional filters and pagination.\n */\n async list(params?: ListParams): Promise<PaginatedResponse<TEntity>> {\n return this.http.request<PaginatedResponse<TEntity>>({\n method: 'GET',\n path: this.basePath,\n params: params as Record<string, unknown>,\n });\n }\n\n /**\n * Get a single entity by ID.\n */\n async get(id: string): Promise<TEntity> {\n return this.http.request<TEntity>({\n method: 'GET',\n path: `${this.basePath}/${id}`,\n });\n }\n\n /**\n * Create a new entity.\n */\n async create(data: TCreate): Promise<TEntity> {\n return this.http.request<TEntity>({\n method: 'POST',\n path: this.basePath,\n body: data,\n });\n }\n\n /**\n * Update an existing entity.\n */\n async update(id: string, data: TUpdate): Promise<TEntity> {\n return this.http.request<TEntity>({\n method: 'PATCH',\n path: `${this.basePath}/${id}`,\n body: data,\n });\n }\n\n /**\n * Delete an entity by ID.\n */\n async delete(id: string): Promise<void> {\n await this.http.request<void>({\n method: 'DELETE',\n path: `${this.basePath}/${id}`,\n });\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Opportunity resource — CRUD operations on /opportunities. */\nexport class OpportunityResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/opportunities');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Customer resource — CRUD operations on /customers. */\nexport class CustomerResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/customers');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Property resource — CRUD operations on /properties. */\nexport class PropertyResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/properties');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Calendar event resource — CRUD operations on /calendar-events. */\nexport class CalendarEventResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/calendar-events');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Contract resource — CRUD operations on /contracts. */\nexport class ContractResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/contracts');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Job resource — CRUD operations on /jobs. */\nexport class JobResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/jobs');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Estimate resource — CRUD operations on /estimates. */\nexport class EstimateResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/estimates');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Invoice resource — CRUD operations on /invoices. */\nexport class InvoiceResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/invoices');\n }\n}\n","import { BaseResource } from './base-resource';\n\n/** Inventory resource — CRUD operations on /inventory/items. */\nexport class InventoryResource extends BaseResource {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/inventory/items');\n }\n}\n","import { BaseResource } from './base-resource';\nimport type { HttpClient } from '../http';\nimport type {\n ContactDto,\n CreateContactDto,\n UpdateContactDto,\n ContactChannelDto,\n CreateContactChannelDto,\n UpdateContactChannelDto,\n AddContactChannelResult,\n ContactMergeResult,\n} from '../types/contact-types';\n\n/**\n * `/contacts/:id/channels` — POST/PATCH/DELETE only. There is no list/get\n * route: reads come embedded in the contact detail response (`GET\n * /contacts/:id` returns `{ ...contact, channels, links, mergedInto }`), so\n * this does NOT extend `BaseResource`.\n */\nexport class ContactChannelResource {\n private readonly basePath: string;\n\n constructor(\n private readonly http: HttpClient,\n contactId: string,\n ) {\n this.basePath = `/contacts/${contactId}/channels`;\n }\n\n /** Returns `{ channel, duplicateOf }` — `duplicateOf` is the dedup signal. */\n async create(data: CreateContactChannelDto): Promise<AddContactChannelResult> {\n return this.http.request<AddContactChannelResult>({\n method: 'POST',\n path: this.basePath,\n body: data,\n });\n }\n\n async update(channelId: string, data: UpdateContactChannelDto): Promise<ContactChannelDto> {\n return this.http.request<ContactChannelDto>({\n method: 'PATCH',\n path: `${this.basePath}/${channelId}`,\n body: data,\n });\n }\n\n async delete(channelId: string): Promise<void> {\n await this.http.request<void>({\n method: 'DELETE',\n path: `${this.basePath}/${channelId}`,\n });\n }\n}\n\nexport class ContactResource extends BaseResource<\n ContactDto,\n CreateContactDto,\n UpdateContactDto\n> {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/contacts');\n }\n\n async merge(winnerId: string, loserId: string): Promise<ContactMergeResult> {\n return this.http.request<ContactMergeResult>({\n method: 'POST',\n path: `/contacts/${winnerId}/merge/${loserId}`,\n });\n }\n\n channels(contactId: string): ContactChannelResource {\n return new ContactChannelResource(this.http, contactId);\n }\n}\n","import type { HttpClient } from '../http';\nimport type { PaginatedResponse, ListParams } from '../types';\nimport type { ActivityDto, CreateActivityDto } from '../types/activity-types';\n\nexport class ActivityResource {\n constructor(private readonly http: HttpClient) {}\n\n async create(data: CreateActivityDto): Promise<ActivityDto> {\n return this.http.request<ActivityDto>({\n method: 'POST',\n path: '/activities',\n body: data,\n });\n }\n\n async list(params?: ListParams): Promise<PaginatedResponse<ActivityDto>> {\n return this.http.request<PaginatedResponse<ActivityDto>>({\n method: 'GET',\n path: '/activities',\n params: params as Record<string, unknown>,\n });\n }\n\n async listForEntity(\n entityType: string,\n entityId: string,\n params?: ListParams,\n ): Promise<PaginatedResponse<ActivityDto>> {\n return this.http.request<PaginatedResponse<ActivityDto>>({\n method: 'GET',\n path: `/${entityType}/${entityId}/activities`,\n params: params as Record<string, unknown>,\n });\n }\n}\n","import { BaseResource } from './base-resource';\nimport type {\n TagDto,\n CreateTagDto,\n UpdateTagDto,\n SetEntityTagsDto,\n EntityTagDto,\n TagEntitiesPage,\n} from '../types/tag-types';\n\nexport class TagResource extends BaseResource<TagDto, CreateTagDto, UpdateTagDto> {\n constructor(http: ConstructorParameters<typeof BaseResource>[0]) {\n super(http, '/tags');\n }\n\n /** `TagsService.setEntityTags()` returns the final tag set wrapped in `{ tags }`. */\n async setEntityTags(\n entityType: string,\n entityId: string,\n data: SetEntityTagsDto,\n ): Promise<{ tags: TagDto[] }> {\n return this.http.request<{ tags: TagDto[] }>({\n method: 'PUT',\n path: `/entities/${entityType}/${entityId}/tags`,\n body: data,\n });\n }\n\n async getEntityTags(entityType: string, entityId: string): Promise<EntityTagDto[]> {\n return this.http.request<EntityTagDto[]>({\n method: 'GET',\n path: `/entities/${entityType}/${entityId}/tags`,\n });\n }\n\n async getEntities(tagId: string, params?: Record<string, unknown>): Promise<TagEntitiesPage> {\n return this.http.request<TagEntitiesPage>({\n method: 'GET',\n path: `/tags/${tagId}/entities`,\n params: params as Record<string, unknown>,\n });\n }\n}\n","import { createHmac, timingSafeEqual } from 'node:crypto';\nimport { WebhookVerificationError } from './errors';\n\n/**\n * The kinds of change a webhook event can describe.\n *\n * Mirrored (self-contained, NOT imported) from\n * `packages/shared/src/types/entity-events.ts` — the SDK must not depend on\n * `@blueprint/shared`. Keep this union byte-identical to that source of truth.\n */\nexport type EntityEventType =\n | 'created'\n | 'updated'\n | 'deleted'\n | 'status_changed'\n | 'entity.tagged'\n | 'entity.untagged'\n | 'list.member_added'\n | 'list.member_removed'\n | 'entity.assigned'\n | 'entity.unassigned'\n | 'team.member_added'\n | 'team.member_removed'\n | 'task.created'\n | 'task.updated'\n | 'task.completed'\n | 'task.cancelled'\n | 'comment.created';\n\n/**\n * The entity kinds a webhook event can target.\n *\n * Mirrored (self-contained, NOT imported) from\n * `packages/shared/src/types/entity-events.ts`. Keep byte-identical.\n */\nexport type EntityType =\n | 'customer'\n | 'opportunity'\n | 'job'\n | 'estimate'\n | 'contract'\n | 'property'\n | 'inventory_item'\n | 'agent_execution'\n | 'calendar_event'\n | 'invoice'\n | 'payment';\n\n/**\n * A single entity-change event as emitted onto the Planes event bus and\n * delivered inside a {@link WebhookDelivery}.\n *\n * Mirrored from `EntityEvent` in\n * `packages/shared/src/types/entity-events.ts`.\n */\nexport interface EntityEvent {\n /** What happened (e.g. 'created', 'status_changed'). */\n event_type: EntityEventType;\n /** The kind of record this event is about. */\n entity_type: EntityType;\n /** The affected record's ID. */\n entity_id: string;\n /** Tenant ID that owns this event. */\n tenant_id: string;\n /** Per-field before/after values for the change. */\n changes: Record<string, { old: unknown; new: unknown }>;\n /** Full record snapshot at the time of the event. */\n snapshot: Record<string, unknown>;\n /** ISO 8601 timestamp of when the event occurred. */\n timestamp: string;\n /** Correlation ID linking related events/actions. */\n correlation_id: string;\n}\n\n/**\n * The webhook delivery envelope Planes POSTs to a subscriber's URL.\n *\n * This is the outermost body: a delivery ID, the schema `version`, the wrapped\n * {@link EntityEvent}, and the subscription that matched. The entity body lives\n * at `event.snapshot` — there is no top-level `data`.\n */\nexport interface WebhookDelivery {\n /** Delivery ID (the delivery-log row id). */\n id: string;\n /**\n * Webhook payload schema version (from the `X-Planes-Webhook-Version`\n * header / envelope, emitted API-side per E39-F1-S2). v1 requires no\n * branching; surfaced here so consumers can switch on it in the future.\n */\n version: number;\n /** The entity-change event that triggered this delivery. */\n event: EntityEvent;\n /** ID of the webhook subscription that matched and produced this delivery. */\n subscription_id: string;\n}\n\n/**\n * Verify an HMAC-SHA256 webhook signature using timing-safe comparison.\n *\n * The signature header format is: `sha256=<hex-digest>`\n *\n * @param payload - The raw request body (string or Buffer)\n * @param signature - The X-Planes-Signature header value\n * @param secret - The webhook signing secret\n * @returns true if the signature is valid, false otherwise\n */\nexport function verifyWebhookSignature(\n payload: string | Buffer,\n signature: string,\n secret: string,\n): boolean {\n if (!signature || !secret || !payload) {\n return false;\n }\n\n // Expect format: sha256=<hex>\n const prefix = 'sha256=';\n if (!signature.startsWith(prefix)) {\n return false;\n }\n\n const receivedHex = signature.slice(prefix.length);\n if (!receivedHex) {\n return false;\n }\n\n const expectedHmac = createHmac('sha256', secret)\n .update(payload)\n .digest('hex');\n\n // Convert to buffers for timing-safe comparison\n const receivedBuf = Buffer.from(receivedHex, 'hex');\n const expectedBuf = Buffer.from(expectedHmac, 'hex');\n\n if (receivedBuf.length !== expectedBuf.length) {\n return false;\n }\n\n return timingSafeEqual(receivedBuf, expectedBuf);\n}\n\n/**\n * Parse and verify a webhook delivery payload.\n *\n * @param body - The raw request body as a string\n * @param signature - The X-Planes-Signature header value\n * @param secret - The webhook signing secret\n * @returns The parsed and verified {@link WebhookDelivery} envelope\n * @throws WebhookVerificationError if signature is invalid\n */\nexport function parseWebhookEvent(\n body: string,\n signature: string,\n secret: string,\n): WebhookDelivery {\n if (!verifyWebhookSignature(body, signature, secret)) {\n throw new WebhookVerificationError();\n }\n\n return JSON.parse(body) as WebhookDelivery;\n}\n","import { parseWebhookEvent, type WebhookDelivery } from './webhooks';\n\n/**\n * A handler invoked for a matching webhook delivery. May be sync or async;\n * async handlers are awaited by {@link WebhookRouter.process}.\n */\nexport type WebhookHandler = (delivery: WebhookDelivery) => void | Promise<void>;\n\n/** Input to {@link WebhookRouter.process} — the raw request pieces. */\nexport interface WebhookProcessInput {\n /** The raw request body as a string (verbatim — not re-serialized JSON). */\n body: string;\n /** The `X-Planes-Signature` header value (`sha256=<hex>`). */\n signature: string;\n /** The webhook signing secret for this subscription. */\n secret: string;\n}\n\n/**\n * Ergonomic event router over the corrected {@link WebhookDelivery} envelope.\n *\n * The platform models an event as two fields — `event.entity_type` and\n * `event.event_type` — which this router joins into a dotted composite key\n * `` `${entity_type}.${event_type}` `` (e.g. `opportunity.created`). Handlers\n * subscribe to that key, to an entity-scoped wildcard `` `${entity_type}.*` ``\n * (e.g. `opportunity.*`), or to the global wildcard `*`.\n *\n * The router is a pure dispatcher: it verifies + parses the delivery, then\n * fans it out to matching handlers. It holds no connection and no auth — the\n * signing secret is supplied per {@link process} call — so the same client's\n * `webhooks` router works across subscriptions. Wire it into your own HTTP\n * handler.\n *\n * @example\n * ```ts\n * client.webhooks\n * .on('opportunity.created', (d) => console.log(d.event.snapshot))\n * .on('opportunity.*', (d) => audit(d))\n * .on('*', (d) => metrics.count(d.event.entity_type));\n *\n * // inside your POST handler:\n * await client.webhooks.process({ body: rawBody, signature, secret });\n * ```\n *\n * ### Dotted `event_type` composite keys\n *\n * Four event types are themselves dotted — `entity.tagged`, `entity.untagged`,\n * `list.member_added`, `list.member_removed`. Joined with their entity these\n * yield keys with more than one dot, e.g. `customer.entity.tagged`. This router\n * matches on the literal `` `${entity_type}.${event_type}` `` string, so\n * subscribe with the full key (`'customer.entity.tagged'`). The entity wildcard\n * (`'customer.*'`) and global wildcard (`'*'`) still match these deliveries;\n * only mid-segment wildcards like `'customer.entity.*'` are intentionally\n * unsupported.\n */\nexport class WebhookRouter {\n /** pattern (composite key / entity wildcard / '*') → registered handlers. */\n private readonly handlers = new Map<string, WebhookHandler[]>();\n\n /**\n * Register a handler for an event pattern.\n *\n * @param pattern - `\"{entity_type}.{event_type}\"` (e.g. `opportunity.created`),\n * an entity wildcard `\"{entity_type}.*\"` (e.g. `opportunity.*`), or the\n * global wildcard `\"*\"`. Matching is exact-string against the derived key;\n * see the class docs for the dotted-`event_type` shape.\n * @param handler - Invoked with the full {@link WebhookDelivery} on a match.\n * @returns `this`, for chaining.\n */\n on(pattern: string, handler: WebhookHandler): this {\n const existing = this.handlers.get(pattern);\n if (existing) {\n existing.push(handler);\n } else {\n this.handlers.set(pattern, [handler]);\n }\n return this;\n }\n\n /**\n * Verify, parse, and dispatch a webhook delivery.\n *\n * The signature is verified first: on failure this throws\n * {@link WebhookVerificationError} and dispatches nothing. On success it\n * dispatches to every matching handler — specific key, then entity wildcard,\n * then global — awaiting async handlers before resolving.\n *\n * @param input - The raw `{ body, signature, secret }`.\n * @returns The parsed {@link WebhookDelivery}.\n * @throws WebhookVerificationError if the signature is invalid.\n */\n async process(input: WebhookProcessInput): Promise<WebhookDelivery> {\n // Verify + parse first; throws WebhookVerificationError before any dispatch.\n const delivery = parseWebhookEvent(input.body, input.signature, input.secret);\n\n const { entity_type, event_type } = delivery.event;\n // Candidate patterns, most specific first. Deriving them from the delivery\n // (rather than scanning registered patterns) keeps dispatch O(1) per key\n // and correctly handles dotted event_types like `entity.tagged`.\n const patterns = [`${entity_type}.${event_type}`, `${entity_type}.*`, '*'];\n\n for (const pattern of patterns) {\n const matched = this.handlers.get(pattern);\n if (!matched) continue;\n for (const handler of matched) {\n await handler(delivery);\n }\n }\n\n return delivery;\n }\n}\n","import type { PlanesConfig } from './config';\nimport { HttpClient } from './http';\nimport { OpportunityResource } from './resources/opportunities';\nimport { CustomerResource } from './resources/customers';\nimport { PropertyResource } from './resources/properties';\nimport { CalendarEventResource } from './resources/calendar-events';\nimport { ContractResource } from './resources/contracts';\nimport { JobResource } from './resources/jobs';\nimport { EstimateResource } from './resources/estimates';\nimport { InvoiceResource } from './resources/invoices';\nimport { InventoryResource } from './resources/inventory';\nimport { ContactResource } from './resources/contacts';\nimport { ActivityResource } from './resources/activities';\nimport { TagResource } from './resources/tags';\nimport { WebhookRouter } from './webhook-router';\n\n/**\n * PlanesClient — typed API client for the Planes CRM platform.\n *\n * @example\n * ```ts\n * const client = new PlanesClient({ accessToken: 'tok_...' });\n * const opps = await client.opportunities.list({ page: 1, limit: 20 });\n * ```\n */\nexport class PlanesClient {\n private readonly http: HttpClient;\n private _opportunities?: OpportunityResource;\n private _customers?: CustomerResource;\n private _properties?: PropertyResource;\n private _calendarEvents?: CalendarEventResource;\n private _contracts?: ContractResource;\n private _jobs?: JobResource;\n private _estimates?: EstimateResource;\n private _invoices?: InvoiceResource;\n private _inventory?: InventoryResource;\n private _contacts?: ContactResource;\n private _activities?: ActivityResource;\n private _tags?: TagResource;\n private _webhooks?: WebhookRouter;\n\n constructor(config: PlanesConfig) {\n const hasOAuthCreds = Boolean(config.clientId) || Boolean(config.clientSecret);\n const modes = [\n config.accessToken ? 'accessToken' : null,\n config.apiKey ? 'apiKey' : null,\n hasOAuthCreds ? 'clientId+clientSecret' : null,\n ].filter((m): m is string => m !== null);\n\n if (modes.length === 0) {\n throw new Error(\n 'PlanesClient requires one auth mode: accessToken, apiKey, or clientId + clientSecret',\n );\n }\n if (modes.length > 1) {\n throw new Error(\n `PlanesClient requires exactly one auth mode, not multiple (got: ${modes.join(', ')})`,\n );\n }\n if (hasOAuthCreds && (!config.clientId || !config.clientSecret)) {\n throw new Error('PlanesClient OAuth mode requires both clientId and clientSecret');\n }\n\n this.http = new HttpClient(config);\n }\n\n get opportunities(): OpportunityResource {\n if (!this._opportunities) {\n this._opportunities = new OpportunityResource(this.http);\n }\n return this._opportunities;\n }\n\n get customers(): CustomerResource {\n if (!this._customers) {\n this._customers = new CustomerResource(this.http);\n }\n return this._customers;\n }\n\n get properties(): PropertyResource {\n if (!this._properties) {\n this._properties = new PropertyResource(this.http);\n }\n return this._properties;\n }\n\n get calendarEvents(): CalendarEventResource {\n if (!this._calendarEvents) {\n this._calendarEvents = new CalendarEventResource(this.http);\n }\n return this._calendarEvents;\n }\n\n get contracts(): ContractResource {\n if (!this._contracts) {\n this._contracts = new ContractResource(this.http);\n }\n return this._contracts;\n }\n\n get jobs(): JobResource {\n if (!this._jobs) {\n this._jobs = new JobResource(this.http);\n }\n return this._jobs;\n }\n\n get estimates(): EstimateResource {\n if (!this._estimates) {\n this._estimates = new EstimateResource(this.http);\n }\n return this._estimates;\n }\n\n get invoices(): InvoiceResource {\n if (!this._invoices) {\n this._invoices = new InvoiceResource(this.http);\n }\n return this._invoices;\n }\n\n get inventory(): InventoryResource {\n if (!this._inventory) {\n this._inventory = new InventoryResource(this.http);\n }\n return this._inventory;\n }\n\n get contacts(): ContactResource {\n if (!this._contacts) {\n this._contacts = new ContactResource(this.http);\n }\n return this._contacts;\n }\n\n get activities(): ActivityResource {\n if (!this._activities) {\n this._activities = new ActivityResource(this.http);\n }\n return this._activities;\n }\n\n get tags(): TagResource {\n if (!this._tags) {\n this._tags = new TagResource(this.http);\n }\n return this._tags;\n }\n\n /**\n * Ergonomic webhook event router — `client.webhooks.on(pattern, handler)`\n * plus `client.webhooks.process({ body, signature, secret })`. Auth-independent\n * (the signing secret is supplied per `.process()` call), so it is created\n * lazily without the HTTP client. See {@link WebhookRouter}.\n */\n get webhooks(): WebhookRouter {\n if (!this._webhooks) {\n this._webhooks = new WebhookRouter();\n }\n return this._webhooks;\n }\n}\n","import * as http from 'node:http';\nimport type {\n ActionHandler,\n ActionContext,\n ActionResult,\n ActionStage,\n ActionMetadata,\n} from './types/agent-types';\n\n/**\n * The raw wire envelope the Planes platform POSTs to an agent endpoint.\n * Mirrors the `payload` built in `external-agent.handler.ts`. The SDK server\n * flattens `action_context` and lifts the callback fields onto {@link ActionContext}\n * before handing it to a handler.\n */\ninterface WireEnvelope {\n action_context?: {\n action_id: string;\n action_type: string;\n entity_type: string;\n entity_id: string;\n tenant_id: string;\n data?: Record<string, unknown>;\n stage?: ActionStage | null;\n metadata: ActionMetadata;\n };\n callback_token: string;\n callback_base_url: string;\n}\n\n/**\n * Options for Planes.register() and AgentApp.listen().\n */\nexport interface RegisterOptions {\n /** Port to listen on (default 3000). */\n port?: number;\n /** HTTP path to listen on (default '/actions'). */\n path?: string;\n /** Whether to verify incoming request signatures (default true). */\n verifySignature?: boolean;\n /** Error callback for unhandled errors. */\n onError?: (error: Error) => void;\n}\n\n/**\n * Shared server/dispatch factory used by both {@link Planes.register} and\n * {@link AgentApp.listen}. Given a resolved handler map, it creates an HTTP\n * server that parses the platform's `{ action_context, callback_token,\n * callback_base_url }` POST envelope, dispatches to the matching handler by\n * `action_context.action_type`, and serializes the {@link ActionResult} back as\n * the synchronous HTTP response body (which is exactly how the platform's\n * external-agent handler consumes the result).\n *\n * Keeping this in one place ensures the low-level `Planes.register` array\n * API and the `createAgentApp()` builder are guaranteed to behave identically\n * (routing, 400/404 handling, handler-error capture, graceful shutdown).\n */\nfunction createActionServer(\n handlerMap: Map<string, ActionHandler>,\n options?: RegisterOptions,\n): http.Server {\n const port = options?.port ?? 3000;\n const basePath = options?.path ?? '/actions';\n const onError = options?.onError;\n\n const server = http.createServer(async (req: http.IncomingMessage, res: http.ServerResponse) => {\n // Only handle POST to the configured path\n if (req.method !== 'POST' || req.url !== basePath) {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not found' }));\n return;\n }\n\n try {\n const body = await readBody(req);\n\n // The platform POSTs a wrapped envelope:\n // { action_context: { action_type, ... }, callback_token, callback_base_url }\n // (see apps/api/.../external-agent.handler.ts). Parse and validate it.\n let envelope: WireEnvelope;\n try {\n envelope = JSON.parse(body) as WireEnvelope;\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Malformed request body' }));\n return;\n }\n\n const wire = envelope?.action_context;\n const actionType = wire?.action_type;\n if (!wire || !actionType) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({ error: 'Missing action_context.action_type in request body' }),\n );\n return;\n }\n\n const handler = handlerMap.get(actionType);\n if (!handler) {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n error: `No handler registered for action type: ${actionType}`,\n }),\n );\n return;\n }\n\n // Flatten the wire `action_context` and lift the envelope-level callback\n // fields + the X-Planes-Request-Id header onto a single handler context.\n const requestIdHeader = req.headers['x-planes-request-id'];\n const requestId = Array.isArray(requestIdHeader) ? requestIdHeader[0] : requestIdHeader;\n const context: ActionContext = {\n action_id: wire.action_id,\n action_type: actionType,\n entity_type: wire.entity_type,\n entity_id: wire.entity_id,\n tenant_id: wire.tenant_id,\n data: wire.data ?? {},\n stage: wire.stage ?? null,\n metadata: wire.metadata,\n callback_token: envelope.callback_token,\n callback_base_url: envelope.callback_base_url,\n // Only set request_id when the header was present (exactOptionalPropertyTypes).\n ...(requestId !== undefined ? { request_id: requestId } : {}),\n };\n\n let result: ActionResult;\n try {\n result = await handler.execute(context);\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : 'Unknown handler error';\n if (onError && err instanceof Error) {\n onError(err);\n }\n result = {\n status: 'failed',\n error: errorMessage,\n retryable: false,\n };\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(result));\n } catch (err) {\n if (onError && err instanceof Error) {\n onError(err);\n }\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Internal server error' }));\n }\n });\n\n server.listen(port);\n\n // Graceful shutdown\n const shutdown = () => {\n server.close();\n };\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n return server;\n}\n\n/**\n * Planes agent registration namespace.\n * Provides a convenient way to register action handlers as an HTTP server.\n */\nexport const Planes = {\n /**\n * Register action handlers and start an HTTP server to receive action requests.\n *\n * @example\n * ```ts\n * Planes.register([\n * {\n * type: 'send_email',\n * async execute(ctx) {\n * // ... send email logic\n * return { status: 'completed', output: { sent: true } };\n * },\n * },\n * ], { port: 3000 });\n * ```\n */\n register(handlers: ActionHandler[], options?: RegisterOptions): http.Server {\n // Build handler map for O(1) lookup\n const handlerMap = new Map<string, ActionHandler>();\n for (const handler of handlers) {\n handlerMap.set(handler.type, handler);\n }\n return createActionServer(handlerMap, options);\n },\n};\n\n/**\n * Builder-style agent app. Register action handlers incrementally by type,\n * then start the server with {@link AgentApp.listen}.\n *\n * This is ergonomic sugar over {@link Planes.register} — both share the same\n * underlying server/dispatch implementation ({@link createActionServer}).\n *\n * @example\n * ```ts\n * createAgentApp()\n * .registerAction('enrich-opportunity', async (ctx) => {\n * // ... enrichment logic\n * return { status: 'completed', output: { enriched: true } };\n * })\n * .listen({ port: 3000 });\n * ```\n */\nexport class AgentApp {\n private readonly handlers = new Map<string, ActionHandler>();\n\n /**\n * Register a handler for an action type. Accepts a bare `execute` function\n * (the caller does not build the `{ type, execute }` object).\n *\n * @param type - The action type this handler responds to.\n * @param execute - The handler function invoked with the {@link ActionContext}.\n * @returns `this`, for chaining.\n */\n registerAction(type: string, execute: ActionHandler['execute']): this {\n this.handlers.set(type, { type, execute });\n return this;\n }\n\n /**\n * Start an HTTP server that dispatches incoming action requests to the\n * registered handlers. Delegates to the same server factory as\n * {@link Planes.register}.\n *\n * The handler set is snapshotted at call time, so registering further actions\n * after `listen()` does not mutate the running server (parity with\n * `Planes.register`, which snapshots its array).\n */\n listen(options?: RegisterOptions): http.Server {\n return createActionServer(new Map(this.handlers), options);\n }\n}\n\n/**\n * Create a new {@link AgentApp} builder.\n */\nexport function createAgentApp(): AgentApp {\n return new AgentApp();\n}\n\nfunction readBody(req: http.IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks).toString()));\n req.on('error', reject);\n });\n}\n"],"mappings":";AAgCO,IAAM,qBAAN,MAAiD;AAAA,EAGtD,YAAY,MAA4B;AACtC,SAAK,SAAS,QAAQ;AAAA,EACxB;AAAA,EAEA,MAAM,YAA0C;AAC9C,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,UAAU,QAAqC;AACnD,SAAK,SAAS;AAAA,EAChB;AACF;AA6CO,IAAM,mBAAmB;AACzB,IAAM,kBAAkB;AACxB,IAAM,sBAAsB;AAC5B,IAAM,gCAAgC;;;AC3FtC,IAAM,cAAN,cAA0B,MAAM;AAAA,EAIrC,YAAY,SAAiB,QAAgB,MAAc;AACzD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,sBAAN,cAAkC,YAAY;AAAA,EACnD,YAAY,UAAU,yBAAyB;AAC7C,UAAM,SAAS,KAAK,sBAAsB;AAC1C,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,qBAAN,cAAiC,YAAY;AAAA,EAClD,YAAY,UAAU,4BAA4B;AAChD,UAAM,SAAS,KAAK,qBAAqB;AACzC,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,gBAAN,cAA4B,YAAY;AAAA,EAC7C,YAAY,UAAU,sBAAsB;AAC1C,UAAM,SAAS,KAAK,WAAW;AAC/B,SAAK,OAAO;AAAA,EACd;AACF;AAYO,IAAM,kBAAN,cAA8B,YAAY;AAAA,EAG/C,YAAY,UAAU,qBAAqB,cAA4B,CAAC,GAAG;AACzE,UAAM,SAAS,KAAK,kBAAkB;AACtC,SAAK,OAAO;AACZ,SAAK,cAAc;AAAA,EACrB;AACF;AAKO,IAAM,iBAAN,cAA6B,YAAY;AAAA,EAG9C,YAAY,aAAa,GAAG,UAAU,uBAAuB;AAC3D,UAAM,SAAS,KAAK,kBAAkB;AACtC,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAKO,IAAM,cAAN,cAA0B,YAAY;AAAA,EAC3C,YAAY,UAAU,yBAAyB;AAC7C,UAAM,SAAS,KAAK,cAAc;AAClC,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,2BAAN,cAAuC,YAAY;AAAA,EACxD,YAAY,UAAU,yCAAyC;AAC7D,UAAM,SAAS,KAAK,4BAA4B;AAChD,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,aAAN,cAAyB,YAAY;AAAA,EAG1C,YAAY,WAAmB,UAAU,eAAe;AACtD,UAAM,SAAS,KAAK,aAAa;AACjC,SAAK,OAAO;AACZ,SAAK,YAAY;AAAA,EACnB;AACF;;;AC7GA,SAAS,mBAAmB;AAiFrB,SAAS,kBAAkB,SAAmC;AACnE,QAAM,QAAQ,QAAQ,SAAS,YAAY,EAAE,EAAE,SAAS,KAAK;AAC7D,QAAM,UAAU,QAAQ,QAAQ,QAAQ,QAAQ,EAAE;AAElD,QAAM,MAAM,IAAI,IAAI,GAAG,OAAO,kBAAkB;AAChD,MAAI,aAAa,IAAI,aAAa,QAAQ,QAAQ;AAClD,MAAI,aAAa,IAAI,gBAAgB,QAAQ,WAAW;AACxD,MAAI,aAAa,IAAI,SAAS,QAAQ,OAAO,KAAK,GAAG,CAAC;AACtD,MAAI,aAAa,IAAI,iBAAiB,MAAM;AAC5C,MAAI,aAAa,IAAI,SAAS,KAAK;AAEnC,SAAO,IAAI,SAAS;AACtB;AASA,eAAsB,aAAa,SAAsD;AACvF,QAAM,UAAU,QAAQ,QAAQ,QAAQ,QAAQ,EAAE;AAClD,QAAM,MAAM,GAAG,OAAO;AAEtB,QAAM,OAAO,IAAI,gBAAgB;AAAA,IAC/B,YAAY;AAAA,IACZ,WAAW,QAAQ;AAAA,IACnB,eAAe,QAAQ;AAAA,IACvB,MAAM,QAAQ;AAAA,IACd,cAAc,QAAQ;AAAA,EACxB,CAAC;AAED,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,oCAAoC;AAAA,IAC/D,MAAM,KAAK,SAAS;AAAA,EACtB,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACxD,UAAM,YAAa,UAAU,OAAO,KAAgB;AACpD,UAAM,mBAAoB,UAAU,mBAAmB,KAAgB;AACvE,UAAM,IAAI,WAAW,WAAW,gBAAgB;AAAA,EAClD;AAEA,SAAQ,MAAM,SAAS,KAAK;AAC9B;AASA,eAAsB,mBAAmB,SAAiD;AACxF,QAAM,UAAU,QAAQ,QAAQ,QAAQ,QAAQ,EAAE;AAClD,QAAM,MAAM,GAAG,OAAO;AAEtB,QAAM,OAAO,IAAI,gBAAgB;AAAA,IAC/B,YAAY;AAAA,IACZ,WAAW,QAAQ;AAAA,IACnB,eAAe,QAAQ;AAAA,IACvB,eAAe,QAAQ;AAAA,EACzB,CAAC;AAED,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,oCAAoC;AAAA,IAC/D,MAAM,KAAK,SAAS;AAAA,EACtB,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACxD,UAAM,YAAa,UAAU,OAAO,KAAgB;AACpD,UAAM,mBAAoB,UAAU,mBAAmB,KAAgB;AACvE,UAAM,IAAI,WAAW,WAAW,gBAAgB;AAAA,EAClD;AAEA,SAAQ,MAAM,SAAS,KAAK;AAC9B;AAUA,eAAsB,uBACpB,SACwB;AACxB,QAAM,UAAU,QAAQ,QAAQ,QAAQ,QAAQ,EAAE;AAClD,QAAM,MAAM,GAAG,OAAO;AAEtB,QAAM,OAAO,IAAI,gBAAgB;AAAA,IAC/B,YAAY;AAAA,IACZ,WAAW,QAAQ;AAAA,IACnB,eAAe,QAAQ;AAAA,IACvB,OAAO,QAAQ,OAAO,KAAK,GAAG;AAAA,EAChC,CAAC;AAED,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,oCAAoC;AAAA,IAC/D,MAAM,KAAK,SAAS;AAAA,EACtB,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,YAAa,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACzD,UAAM,YAAa,UAAU,OAAO,KAAgB;AACpD,UAAM,mBACH,UAAU,mBAAmB,KAAgB;AAChD,UAAM,IAAI,WAAW,WAAW,gBAAgB;AAAA,EAClD;AAEA,SAAQ,MAAM,SAAS,KAAK;AAC9B;;;AC5JO,IAAM,aAAN,MAAiB;AAAA,EAWtB,YAAY,QAAsB;AAChC,SAAK,WAAW,OAAO,WAAW,kBAAkB,QAAQ,QAAQ,EAAE;AACtE,SAAK,cAAc,OAAO;AAC1B,SAAK,SAAS,OAAO;AACrB,SAAK,UAAU,OAAO,WAAW;AACjC,SAAK,mBAAmB,OAAO,qBAAqB;AACpD,SAAK,aAAa,OAAO,cAAc;AAEvC,QAAI,OAAO,YAAY,OAAO,cAAc;AAC1C,WAAK,QAAQ;AAAA,QACX,UAAU,OAAO;AAAA,QACjB,cAAc,OAAO;AAAA,QACrB,cAAc,OAAO,gBAAgB,IAAI,mBAAmB;AAAA,QAC5D,QAAQ,OAAO,sBAAsB;AAAA,QACrC,QAAQ,OAAO;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QAAW,SAAyC;AACxD,UAAM,MAAM,KAAK,SAAS,QAAQ,MAAM,QAAQ,MAAM;AACtD,UAAM,UAAU,QAAQ,SAAS,SAAY,KAAK,UAAU,QAAQ,IAAI,IAAI;AAE5E,QAAI;AAGJ,QAAI,kBAAkB;AAEtB,aAAS,UAAU,GAAG,WAAW,KAAK,YAAY,WAAW;AAG3D,YAAM,UAAkC;AAAA,QACtC,gBAAgB;AAAA,QAChB,GAAI,MAAM,KAAK,mBAAmB;AAAA,QAClC,GAAG,QAAQ;AAAA,MACb;AACA,YAAM,eAA4B;AAAA,QAChC,QAAQ,QAAQ;AAAA,QAChB;AAAA,QACA,QAAQ,YAAY,QAAQ,KAAK,OAAO;AAAA,MAC1C;AACA,UAAI,YAAY,QAAW;AACzB,qBAAa,OAAO;AAAA,MACtB;AAEA,YAAM,WAAW,MAAM,MAAM,KAAK,YAAY;AAE9C,UAAI,SAAS,IAAI;AAEf,YAAI,SAAS,WAAW,KAAK;AAC3B,iBAAO;AAAA,QACT;AACA,eAAQ,MAAM,SAAS,KAAK;AAAA,MAC9B;AAEA,UAAI,SAAS,WAAW,OAAO,KAAK,oBAAoB,UAAU,KAAK,YAAY;AACjF,cAAM,aAAa,KAAK,gBAAgB,QAAQ;AAChD,cAAM,KAAK,MAAM,aAAa,GAAI;AAClC;AAAA,MACF;AAIA,UAAI,SAAS,WAAW,OAAO,KAAK,SAAS,CAAC,iBAAiB;AAC7D,0BAAkB;AAClB,cAAM,KAAK,cAAc;AACzB;AACA;AAAA,MACF;AAEA,kBAAY,MAAM,KAAK,SAAS,QAAQ;AAGxC,UAAI,SAAS,WAAW,OAAO,CAAC,KAAK,kBAAkB;AACrD,cAAM;AAAA,MACR;AAAA,IACF;AAGA,UAAM,aAAa,IAAI,eAAe;AAAA,EACxC;AAAA,EAEA,iBAAyC;AACvC,QAAI,KAAK,aAAa;AACpB,aAAO,EAAE,eAAe,UAAU,KAAK,WAAW,GAAG;AAAA,IACvD;AACA,QAAI,KAAK,QAAQ;AACf,aAAO,EAAE,aAAa,KAAK,OAAO;AAAA,IACpC;AACA,WAAO,CAAC;AAAA,EACV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,qBAAsD;AAClE,QAAI,CAAC,KAAK,OAAO;AACf,aAAO,KAAK,eAAe;AAAA,IAC7B;AAEA,UAAM,SAAS,MAAM,KAAK,MAAM,aAAa,UAAU;AACvD,QAAI,CAAC,QAAQ;AAGX,aAAO,CAAC;AAAA,IACV;AAEA,QAAI,OAAO,YAAY,KAAK,IAAI,KAAK,KAAK,MAAM,QAAQ;AACtD,YAAM,YAAY,MAAM,KAAK,cAAc;AAC3C,aAAO,EAAE,eAAe,UAAU,UAAU,WAAW,GAAG;AAAA,IAC5D;AAEA,WAAO,EAAE,eAAe,UAAU,OAAO,WAAW,GAAG;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,gBAAuC;AACnD,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI,oBAAoB,6DAA6D;AAAA,IAC7F;AACA,QAAI,KAAK,iBAAiB;AACxB,aAAO,KAAK;AAAA,IACd;AAEA,UAAM,QAAQ,KAAK;AACnB,SAAK,mBAAmB,YAAY;AAClC,YAAM,UAAU,MAAM,MAAM,aAAa,UAAU;AAEnD,UAAI,CAAC,SAAS,cAAc;AAG1B,cAAMA,YAAW,MAAM,uBAAuB;AAAA,UAC5C,SAAS,KAAK;AAAA,UACd,UAAU,MAAM;AAAA,UAChB,cAAc,MAAM;AAAA,UACpB,QAAQ,MAAM,UAAU,CAAC;AAAA,QAC3B,CAAC;AAED,cAAMC,QAAqB;AAAA,UACzB,aAAaD,UAAS;AAAA,UACtB,cAAcA,UAAS,iBAAiB;AAAA,UACxC,WAAW,KAAK,IAAI,IAAIA,UAAS,aAAa;AAAA,QAChD;AACA,cAAM,MAAM,aAAa,UAAUC,KAAI;AACvC,eAAOA;AAAA,MACT;AAEA,YAAM,WAAW,MAAM,mBAAmB;AAAA,QACxC,SAAS,KAAK;AAAA,QACd,UAAU,MAAM;AAAA,QAChB,cAAc,MAAM;AAAA,QACpB,cAAc,QAAQ;AAAA,MACxB,CAAC;AAED,YAAM,OAAqB;AAAA,QACzB,aAAa,SAAS;AAAA,QACtB,cAAc,SAAS,iBAAiB,QAAQ;AAAA,QAChD,WAAW,KAAK,IAAI,IAAI,SAAS,aAAa;AAAA,MAChD;AACA,YAAM,MAAM,aAAa,UAAU,IAAI;AACvC,aAAO;AAAA,IACT,GAAG;AAEH,QAAI;AACF,aAAO,MAAM,KAAK;AAAA,IACpB,UAAE;AACA,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AAAA,EAEQ,SAAS,MAAc,QAA0C;AACvE,UAAM,MAAM,IAAI,IAAI,GAAG,KAAK,OAAO,GAAG,IAAI,EAAE;AAC5C,QAAI,QAAQ;AACV,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,YAAI,UAAU,UAAa,UAAU,MAAM;AACzC,cAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AACA,WAAO,IAAI,SAAS;AAAA,EACtB;AAAA,EAEQ,gBAAgB,UAA4B;AAClD,UAAM,SAAS,SAAS,QAAQ,IAAI,aAAa;AACjD,QAAI,QAAQ;AACV,YAAM,UAAU,OAAO,MAAM;AAC7B,UAAI,CAAC,OAAO,MAAM,OAAO,KAAK,UAAU,GAAG;AACzC,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,SAAS,UAA0C;AAC/D,QAAI,OAAgC,CAAC;AACrC,QAAI;AACF,aAAQ,MAAM,SAAS,KAAK;AAAA,IAC9B,QAAQ;AAAA,IAER;AAEA,UAAM,UAAW,KAAK,SAAS,KAAgB,SAAS,cAAc;AAEtE,YAAQ,SAAS,QAAQ;AAAA,MACvB,KAAK,KAAK;AACR,cAAM,cAAe,KAAK,QAAQ,KAAkC,CAAC;AACrE,eAAO,IAAI,gBAAgB,SAAS,WAAW;AAAA,MACjD;AAAA,MACA,KAAK;AACH,eAAO,IAAI,oBAAoB,OAAO;AAAA,MACxC,KAAK;AACH,eAAO,IAAI,mBAAmB,OAAO;AAAA,MACvC,KAAK;AACH,eAAO,IAAI,cAAc,OAAO;AAAA,MAClC,KAAK,KAAK;AACR,cAAM,aAAa,KAAK,gBAAgB,QAAQ;AAChD,eAAO,IAAI,eAAe,YAAY,OAAO;AAAA,MAC/C;AAAA,MACA;AACE,YAAI,SAAS,UAAU,KAAK;AAC1B,iBAAO,IAAI,YAAY,OAAO;AAAA,QAChC;AACA,eAAO,IAAI,YAAY,SAAS,SAAS,QAAQ,eAAe;AAAA,IACpE;AAAA,EACF;AAAA,EAEQ,MAAM,IAA2B;AACvC,WAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,EACzD;AACF;;;ACzRO,IAAe,eAAf,MAIL;AAAA,EACA,YACqBC,OACA,UACnB;AAFmB,gBAAAA;AACA;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKH,MAAM,KAAK,QAA0D;AACnE,WAAO,KAAK,KAAK,QAAoC;AAAA,MACnD,QAAQ;AAAA,MACR,MAAM,KAAK;AAAA,MACX;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,IAAI,IAA8B;AACtC,WAAO,KAAK,KAAK,QAAiB;AAAA,MAChC,QAAQ;AAAA,MACR,MAAM,GAAG,KAAK,QAAQ,IAAI,EAAE;AAAA,IAC9B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,MAAiC;AAC5C,WAAO,KAAK,KAAK,QAAiB;AAAA,MAChC,QAAQ;AAAA,MACR,MAAM,KAAK;AAAA,MACX,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,IAAY,MAAiC;AACxD,WAAO,KAAK,KAAK,QAAiB;AAAA,MAChC,QAAQ;AAAA,MACR,MAAM,GAAG,KAAK,QAAQ,IAAI,EAAE;AAAA,MAC5B,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,IAA2B;AACtC,UAAM,KAAK,KAAK,QAAc;AAAA,MAC5B,QAAQ;AAAA,MACR,MAAM,GAAG,KAAK,QAAQ,IAAI,EAAE;AAAA,IAC9B,CAAC;AAAA,EACH;AACF;;;ACrEO,IAAM,sBAAN,cAAkC,aAAa;AAAA,EACpD,YAAYC,OAAqD;AAC/D,UAAMA,OAAM,gBAAgB;AAAA,EAC9B;AACF;;;ACJO,IAAM,mBAAN,cAA+B,aAAa;AAAA,EACjD,YAAYC,OAAqD;AAC/D,UAAMA,OAAM,YAAY;AAAA,EAC1B;AACF;;;ACJO,IAAM,mBAAN,cAA+B,aAAa;AAAA,EACjD,YAAYC,OAAqD;AAC/D,UAAMA,OAAM,aAAa;AAAA,EAC3B;AACF;;;ACJO,IAAM,wBAAN,cAAoC,aAAa;AAAA,EACtD,YAAYC,OAAqD;AAC/D,UAAMA,OAAM,kBAAkB;AAAA,EAChC;AACF;;;ACJO,IAAM,mBAAN,cAA+B,aAAa;AAAA,EACjD,YAAYC,OAAqD;AAC/D,UAAMA,OAAM,YAAY;AAAA,EAC1B;AACF;;;ACJO,IAAM,cAAN,cAA0B,aAAa;AAAA,EAC5C,YAAYC,OAAqD;AAC/D,UAAMA,OAAM,OAAO;AAAA,EACrB;AACF;;;ACJO,IAAM,mBAAN,cAA+B,aAAa;AAAA,EACjD,YAAYC,OAAqD;AAC/D,UAAMA,OAAM,YAAY;AAAA,EAC1B;AACF;;;ACJO,IAAM,kBAAN,cAA8B,aAAa;AAAA,EAChD,YAAYC,OAAqD;AAC/D,UAAMA,OAAM,WAAW;AAAA,EACzB;AACF;;;ACJO,IAAM,oBAAN,cAAgC,aAAa;AAAA,EAClD,YAAYC,OAAqD;AAC/D,UAAMA,OAAM,kBAAkB;AAAA,EAChC;AACF;;;ACYO,IAAM,yBAAN,MAA6B;AAAA,EAGlC,YACmBC,OACjB,WACA;AAFiB,gBAAAA;AAGjB,SAAK,WAAW,aAAa,SAAS;AAAA,EACxC;AAAA;AAAA,EAGA,MAAM,OAAO,MAAiE;AAC5E,WAAO,KAAK,KAAK,QAAiC;AAAA,MAChD,QAAQ;AAAA,MACR,MAAM,KAAK;AAAA,MACX,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAO,WAAmB,MAA2D;AACzF,WAAO,KAAK,KAAK,QAA2B;AAAA,MAC1C,QAAQ;AAAA,MACR,MAAM,GAAG,KAAK,QAAQ,IAAI,SAAS;AAAA,MACnC,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAO,WAAkC;AAC7C,UAAM,KAAK,KAAK,QAAc;AAAA,MAC5B,QAAQ;AAAA,MACR,MAAM,GAAG,KAAK,QAAQ,IAAI,SAAS;AAAA,IACrC,CAAC;AAAA,EACH;AACF;AAEO,IAAM,kBAAN,cAA8B,aAInC;AAAA,EACA,YAAYA,OAAqD;AAC/D,UAAMA,OAAM,WAAW;AAAA,EACzB;AAAA,EAEA,MAAM,MAAM,UAAkB,SAA8C;AAC1E,WAAO,KAAK,KAAK,QAA4B;AAAA,MAC3C,QAAQ;AAAA,MACR,MAAM,aAAa,QAAQ,UAAU,OAAO;AAAA,IAC9C,CAAC;AAAA,EACH;AAAA,EAEA,SAAS,WAA2C;AAClD,WAAO,IAAI,uBAAuB,KAAK,MAAM,SAAS;AAAA,EACxD;AACF;;;ACrEO,IAAM,mBAAN,MAAuB;AAAA,EAC5B,YAA6BC,OAAkB;AAAlB,gBAAAA;AAAA,EAAmB;AAAA,EAEhD,MAAM,OAAO,MAA+C;AAC1D,WAAO,KAAK,KAAK,QAAqB;AAAA,MACpC,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,KAAK,QAA8D;AACvE,WAAO,KAAK,KAAK,QAAwC;AAAA,MACvD,QAAQ;AAAA,MACR,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,cACJ,YACA,UACA,QACyC;AACzC,WAAO,KAAK,KAAK,QAAwC;AAAA,MACvD,QAAQ;AAAA,MACR,MAAM,IAAI,UAAU,IAAI,QAAQ;AAAA,MAChC;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ACxBO,IAAM,cAAN,cAA0B,aAAiD;AAAA,EAChF,YAAYC,OAAqD;AAC/D,UAAMA,OAAM,OAAO;AAAA,EACrB;AAAA;AAAA,EAGA,MAAM,cACJ,YACA,UACA,MAC6B;AAC7B,WAAO,KAAK,KAAK,QAA4B;AAAA,MAC3C,QAAQ;AAAA,MACR,MAAM,aAAa,UAAU,IAAI,QAAQ;AAAA,MACzC,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,cAAc,YAAoB,UAA2C;AACjF,WAAO,KAAK,KAAK,QAAwB;AAAA,MACvC,QAAQ;AAAA,MACR,MAAM,aAAa,UAAU,IAAI,QAAQ;AAAA,IAC3C,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,YAAY,OAAe,QAA4D;AAC3F,WAAO,KAAK,KAAK,QAAyB;AAAA,MACxC,QAAQ;AAAA,MACR,MAAM,SAAS,KAAK;AAAA,MACpB;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;AC1CA,SAAS,YAAY,uBAAuB;AA0GrC,SAAS,uBACd,SACA,WACA,QACS;AACT,MAAI,CAAC,aAAa,CAAC,UAAU,CAAC,SAAS;AACrC,WAAO;AAAA,EACT;AAGA,QAAM,SAAS;AACf,MAAI,CAAC,UAAU,WAAW,MAAM,GAAG;AACjC,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,UAAU,MAAM,OAAO,MAAM;AACjD,MAAI,CAAC,aAAa;AAChB,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,WAAW,UAAU,MAAM,EAC7C,OAAO,OAAO,EACd,OAAO,KAAK;AAGf,QAAM,cAAc,OAAO,KAAK,aAAa,KAAK;AAClD,QAAM,cAAc,OAAO,KAAK,cAAc,KAAK;AAEnD,MAAI,YAAY,WAAW,YAAY,QAAQ;AAC7C,WAAO;AAAA,EACT;AAEA,SAAO,gBAAgB,aAAa,WAAW;AACjD;AAWO,SAAS,kBACd,MACA,WACA,QACiB;AACjB,MAAI,CAAC,uBAAuB,MAAM,WAAW,MAAM,GAAG;AACpD,UAAM,IAAI,yBAAyB;AAAA,EACrC;AAEA,SAAO,KAAK,MAAM,IAAI;AACxB;;;ACzGO,IAAM,gBAAN,MAAoB;AAAA,EAApB;AAEL;AAAA,SAAiB,WAAW,oBAAI,IAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY9D,GAAG,SAAiB,SAA+B;AACjD,UAAM,WAAW,KAAK,SAAS,IAAI,OAAO;AAC1C,QAAI,UAAU;AACZ,eAAS,KAAK,OAAO;AAAA,IACvB,OAAO;AACL,WAAK,SAAS,IAAI,SAAS,CAAC,OAAO,CAAC;AAAA,IACtC;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,QAAQ,OAAsD;AAElE,UAAM,WAAW,kBAAkB,MAAM,MAAM,MAAM,WAAW,MAAM,MAAM;AAE5E,UAAM,EAAE,aAAa,WAAW,IAAI,SAAS;AAI7C,UAAM,WAAW,CAAC,GAAG,WAAW,IAAI,UAAU,IAAI,GAAG,WAAW,MAAM,GAAG;AAEzE,eAAW,WAAW,UAAU;AAC9B,YAAM,UAAU,KAAK,SAAS,IAAI,OAAO;AACzC,UAAI,CAAC,QAAS;AACd,iBAAW,WAAW,SAAS;AAC7B,cAAM,QAAQ,QAAQ;AAAA,MACxB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;;;ACtFO,IAAM,eAAN,MAAmB;AAAA,EAgBxB,YAAY,QAAsB;AAChC,UAAM,gBAAgB,QAAQ,OAAO,QAAQ,KAAK,QAAQ,OAAO,YAAY;AAC7E,UAAM,QAAQ;AAAA,MACZ,OAAO,cAAc,gBAAgB;AAAA,MACrC,OAAO,SAAS,WAAW;AAAA,MAC3B,gBAAgB,0BAA0B;AAAA,IAC5C,EAAE,OAAO,CAAC,MAAmB,MAAM,IAAI;AAEvC,QAAI,MAAM,WAAW,GAAG;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,MAAM,SAAS,GAAG;AACpB,YAAM,IAAI;AAAA,QACR,mEAAmE,MAAM,KAAK,IAAI,CAAC;AAAA,MACrF;AAAA,IACF;AACA,QAAI,kBAAkB,CAAC,OAAO,YAAY,CAAC,OAAO,eAAe;AAC/D,YAAM,IAAI,MAAM,iEAAiE;AAAA,IACnF;AAEA,SAAK,OAAO,IAAI,WAAW,MAAM;AAAA,EACnC;AAAA,EAEA,IAAI,gBAAqC;AACvC,QAAI,CAAC,KAAK,gBAAgB;AACxB,WAAK,iBAAiB,IAAI,oBAAoB,KAAK,IAAI;AAAA,IACzD;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,YAA8B;AAChC,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,aAAa,IAAI,iBAAiB,KAAK,IAAI;AAAA,IAClD;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,aAA+B;AACjC,QAAI,CAAC,KAAK,aAAa;AACrB,WAAK,cAAc,IAAI,iBAAiB,KAAK,IAAI;AAAA,IACnD;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,iBAAwC;AAC1C,QAAI,CAAC,KAAK,iBAAiB;AACzB,WAAK,kBAAkB,IAAI,sBAAsB,KAAK,IAAI;AAAA,IAC5D;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,YAA8B;AAChC,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,aAAa,IAAI,iBAAiB,KAAK,IAAI;AAAA,IAClD;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,OAAoB;AACtB,QAAI,CAAC,KAAK,OAAO;AACf,WAAK,QAAQ,IAAI,YAAY,KAAK,IAAI;AAAA,IACxC;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,YAA8B;AAChC,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,aAAa,IAAI,iBAAiB,KAAK,IAAI;AAAA,IAClD;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,WAA4B;AAC9B,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,YAAY,IAAI,gBAAgB,KAAK,IAAI;AAAA,IAChD;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,YAA+B;AACjC,QAAI,CAAC,KAAK,YAAY;AACpB,WAAK,aAAa,IAAI,kBAAkB,KAAK,IAAI;AAAA,IACnD;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,WAA4B;AAC9B,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,YAAY,IAAI,gBAAgB,KAAK,IAAI;AAAA,IAChD;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,aAA+B;AACjC,QAAI,CAAC,KAAK,aAAa;AACrB,WAAK,cAAc,IAAI,iBAAiB,KAAK,IAAI;AAAA,IACnD;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,OAAoB;AACtB,QAAI,CAAC,KAAK,OAAO;AACf,WAAK,QAAQ,IAAI,YAAY,KAAK,IAAI;AAAA,IACxC;AACA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,WAA0B;AAC5B,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,YAAY,IAAI,cAAc;AAAA,IACrC;AACA,WAAO,KAAK;AAAA,EACd;AACF;;;AClKA,YAAY,UAAU;AAyDtB,SAAS,mBACP,YACA,SACa;AACb,QAAM,OAAO,SAAS,QAAQ;AAC9B,QAAM,WAAW,SAAS,QAAQ;AAClC,QAAM,UAAU,SAAS;AAEzB,QAAM,SAAc,kBAAa,OAAO,KAA2B,QAA6B;AAE9F,QAAI,IAAI,WAAW,UAAU,IAAI,QAAQ,UAAU;AACjD,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,YAAY,CAAC,CAAC;AAC9C;AAAA,IACF;AAEA,QAAI;AACF,YAAM,OAAO,MAAM,SAAS,GAAG;AAK/B,UAAI;AACJ,UAAI;AACF,mBAAW,KAAK,MAAM,IAAI;AAAA,MAC5B,QAAQ;AACN,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI,IAAI,KAAK,UAAU,EAAE,OAAO,yBAAyB,CAAC,CAAC;AAC3D;AAAA,MACF;AAEA,YAAM,OAAO,UAAU;AACvB,YAAM,aAAa,MAAM;AACzB,UAAI,CAAC,QAAQ,CAAC,YAAY;AACxB,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI;AAAA,UACF,KAAK,UAAU,EAAE,OAAO,qDAAqD,CAAC;AAAA,QAChF;AACA;AAAA,MACF;AAEA,YAAM,UAAU,WAAW,IAAI,UAAU;AACzC,UAAI,CAAC,SAAS;AACZ,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI;AAAA,UACF,KAAK,UAAU;AAAA,YACb,OAAO,0CAA0C,UAAU;AAAA,UAC7D,CAAC;AAAA,QACH;AACA;AAAA,MACF;AAIA,YAAM,kBAAkB,IAAI,QAAQ,qBAAqB;AACzD,YAAM,YAAY,MAAM,QAAQ,eAAe,IAAI,gBAAgB,CAAC,IAAI;AACxE,YAAM,UAAyB;AAAA,QAC7B,WAAW,KAAK;AAAA,QAChB,aAAa;AAAA,QACb,aAAa,KAAK;AAAA,QAClB,WAAW,KAAK;AAAA,QAChB,WAAW,KAAK;AAAA,QAChB,MAAM,KAAK,QAAQ,CAAC;AAAA,QACpB,OAAO,KAAK,SAAS;AAAA,QACrB,UAAU,KAAK;AAAA,QACf,gBAAgB,SAAS;AAAA,QACzB,mBAAmB,SAAS;AAAA;AAAA,QAE5B,GAAI,cAAc,SAAY,EAAE,YAAY,UAAU,IAAI,CAAC;AAAA,MAC7D;AAEA,UAAI;AACJ,UAAI;AACF,iBAAS,MAAM,QAAQ,QAAQ,OAAO;AAAA,MACxC,SAAS,KAAK;AACZ,cAAM,eACJ,eAAe,QAAQ,IAAI,UAAU;AACvC,YAAI,WAAW,eAAe,OAAO;AACnC,kBAAQ,GAAG;AAAA,QACb;AACA,iBAAS;AAAA,UACP,QAAQ;AAAA,UACR,OAAO;AAAA,UACP,WAAW;AAAA,QACb;AAAA,MACF;AAEA,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,MAAM,CAAC;AAAA,IAChC,SAAS,KAAK;AACZ,UAAI,WAAW,eAAe,OAAO;AACnC,gBAAQ,GAAG;AAAA,MACb;AACA,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,wBAAwB,CAAC,CAAC;AAAA,IAC5D;AAAA,EACF,CAAC;AAED,SAAO,OAAO,IAAI;AAGlB,QAAM,WAAW,MAAM;AACrB,WAAO,MAAM;AAAA,EACf;AACA,UAAQ,GAAG,WAAW,QAAQ;AAC9B,UAAQ,GAAG,UAAU,QAAQ;AAE7B,SAAO;AACT;AAMO,IAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBpB,SAAS,UAA2B,SAAwC;AAE1E,UAAM,aAAa,oBAAI,IAA2B;AAClD,eAAW,WAAW,UAAU;AAC9B,iBAAW,IAAI,QAAQ,MAAM,OAAO;AAAA,IACtC;AACA,WAAO,mBAAmB,YAAY,OAAO;AAAA,EAC/C;AACF;AAmBO,IAAM,WAAN,MAAe;AAAA,EAAf;AACL,SAAiB,WAAW,oBAAI,IAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAU3D,eAAe,MAAc,SAAyC;AACpE,SAAK,SAAS,IAAI,MAAM,EAAE,MAAM,QAAQ,CAAC;AACzC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,OAAO,SAAwC;AAC7C,WAAO,mBAAmB,IAAI,IAAI,KAAK,QAAQ,GAAG,OAAO;AAAA,EAC3D;AACF;AAKO,SAAS,iBAA2B;AACzC,SAAO,IAAI,SAAS;AACtB;AAEA,SAAS,SAAS,KAA4C;AAC5D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,SAAmB,CAAC;AAC1B,QAAI,GAAG,QAAQ,CAAC,UAAkB,OAAO,KAAK,KAAK,CAAC;AACpD,QAAI,GAAG,OAAO,MAAM,QAAQ,OAAO,OAAO,MAAM,EAAE,SAAS,CAAC,CAAC;AAC7D,QAAI,GAAG,SAAS,MAAM;AAAA,EACxB,CAAC;AACH;","names":["response","next","http","http","http","http","http","http","http","http","http","http","http","http","http"]}
package/package.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "@planescrm/sdk",
3
- "version": "0.2.0",
4
- "private": false,
3
+ "version": "0.4.0",
5
4
  "description": "Planes CRM TypeScript SDK — typed client, action handlers, webhooks, and OAuth helpers",
6
5
  "main": "./dist/index.js",
7
6
  "module": "./dist/index.mjs",