@chat-adapter/linear 4.9.0 → 4.9.1
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.
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/cards.ts","../src/markdown.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { extractCard, ValidationError } from \"@chat-adapter/shared\";\nimport type { LinearFetch, User } from \"@linear/sdk\";\nimport { LinearClient } from \"@linear/sdk\";\nimport type {\n Adapter,\n AdapterPostableMessage,\n Author,\n ChatInstance,\n EmojiValue,\n FetchOptions,\n FetchResult,\n FormattedContent,\n Logger,\n RawMessage,\n ThreadInfo,\n WebhookOptions,\n} from \"chat\";\nimport { convertEmojiPlaceholders, Message } from \"chat\";\nimport { cardToLinearMarkdown } from \"./cards\";\nimport { LinearFormatConverter } from \"./markdown\";\nimport type {\n CommentWebhookPayload,\n LinearAdapterConfig,\n LinearCommentData,\n LinearRawMessage,\n LinearThreadId,\n LinearWebhookActor,\n LinearWebhookPayload,\n ReactionWebhookPayload,\n} from \"./types\";\n\n// Re-export types\nexport type {\n LinearAdapterAPIKeyConfig,\n LinearAdapterAppConfig,\n LinearAdapterConfig,\n LinearAdapterOAuthConfig,\n LinearRawMessage,\n LinearThreadId,\n} from \"./types\";\n\n/**\n * Linear adapter for chat SDK.\n *\n * Supports comment threads on Linear issues.\n * Authentication via personal API key or OAuth access token.\n *\n * @example API Key auth\n * ```typescript\n * import { Chat } from \"chat\";\n * import { createLinearAdapter } from \"@chat-adapter/linear\";\n * import { MemoryState } from \"@chat-adapter/state-memory\";\n *\n * const chat = new Chat({\n * userName: \"my-bot\",\n * adapters: {\n * linear: createLinearAdapter({\n * apiKey: process.env.LINEAR_API_KEY!,\n * webhookSecret: process.env.LINEAR_WEBHOOK_SECRET!,\n * userName: \"my-bot\",\n * logger: console,\n * }),\n * },\n * state: new MemoryState(),\n * logger: \"info\",\n * });\n * ```\n *\n * @example OAuth auth\n * ```typescript\n * const chat = new Chat({\n * userName: \"my-bot\",\n * adapters: {\n * linear: createLinearAdapter({\n * accessToken: process.env.LINEAR_ACCESS_TOKEN!,\n * webhookSecret: process.env.LINEAR_WEBHOOK_SECRET!,\n * userName: \"my-bot\",\n * logger: console,\n * }),\n * },\n * state: new MemoryState(),\n * logger: \"info\",\n * });\n * ```\n */\nexport class LinearAdapter\n implements Adapter<LinearThreadId, LinearRawMessage>\n{\n readonly name = \"linear\";\n readonly userName: string;\n\n private linearClient!: LinearClient;\n private webhookSecret: string;\n private chat: ChatInstance | null = null;\n private logger: Logger;\n private _botUserId: string | null = null;\n private formatConverter = new LinearFormatConverter();\n\n // Client credentials auth state\n private clientCredentials: {\n clientId: string;\n clientSecret: string;\n } | null = null;\n private accessTokenExpiry: number | null = null;\n\n /** Bot user ID used for self-message detection */\n get botUserId(): string | undefined {\n return this._botUserId ?? undefined;\n }\n\n constructor(config: LinearAdapterConfig) {\n this.webhookSecret = config.webhookSecret;\n this.logger = config.logger;\n this.userName = config.userName;\n\n // Create LinearClient based on auth method\n // @see https://linear.app/developers/sdk\n if (\"apiKey\" in config && config.apiKey) {\n this.linearClient = new LinearClient({ apiKey: config.apiKey });\n } else if (\"accessToken\" in config && config.accessToken) {\n this.linearClient = new LinearClient({\n accessToken: config.accessToken,\n });\n } else if (\"clientId\" in config && config.clientId) {\n // Client credentials mode - token will be fetched during initialize()\n this.clientCredentials = {\n clientId: config.clientId,\n clientSecret: config.clientSecret,\n };\n } else {\n throw new Error(\n \"LinearAdapter requires either apiKey, accessToken, or clientId/clientSecret\",\n );\n }\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.chat = chat;\n\n // For client credentials mode, fetch an access token first\n if (this.clientCredentials) {\n await this.refreshClientCredentialsToken();\n }\n\n // Fetch the bot's user ID for self-message detection\n // @see https://linear.app/developers/sdk-fetching-and-modifying-data\n try {\n const viewer = await this.linearClient.viewer;\n this._botUserId = viewer.id;\n this.logger.info(\"Linear auth completed\", {\n botUserId: this._botUserId,\n displayName: viewer.displayName,\n });\n } catch (error) {\n this.logger.warn(\"Could not fetch Linear bot user ID\", { error });\n }\n }\n\n /**\n * Fetch a new access token using client credentials grant.\n * The token is valid for 30 days. The adapter auto-refreshes on 401.\n *\n * @see https://linear.app/developers/oauth-2-0-authentication#client-credentials-tokens\n */\n private async refreshClientCredentialsToken(): Promise<void> {\n if (!this.clientCredentials) return;\n\n const { clientId, clientSecret } = this.clientCredentials;\n\n const response = await fetch(\"https://api.linear.app/oauth/token\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: new URLSearchParams({\n grant_type: \"client_credentials\",\n client_id: clientId,\n client_secret: clientSecret,\n scope: \"read,write,comments:create,issues:create\",\n }),\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n throw new Error(\n `Failed to fetch Linear client credentials token: ${response.status} ${errorBody}`,\n );\n }\n\n const data = (await response.json()) as {\n access_token: string;\n expires_in: number;\n };\n\n this.linearClient = new LinearClient({\n accessToken: data.access_token,\n });\n\n // Track expiry so we can proactively refresh (with 1 hour buffer)\n this.accessTokenExpiry = Date.now() + data.expires_in * 1000 - 3600000;\n\n this.logger.info(\"Linear client credentials token obtained\", {\n expiresIn: `${Math.round(data.expires_in / 86400)} days`,\n });\n }\n\n /**\n * Ensure the client credentials token is still valid. Refresh if expired.\n */\n private async ensureValidToken(): Promise<void> {\n if (\n this.clientCredentials &&\n this.accessTokenExpiry &&\n Date.now() > this.accessTokenExpiry\n ) {\n this.logger.info(\"Linear access token expired, refreshing...\");\n await this.refreshClientCredentialsToken();\n }\n }\n\n /**\n * Handle incoming webhook from Linear.\n *\n * @see https://linear.app/developers/webhooks\n */\n async handleWebhook(\n request: Request,\n options?: WebhookOptions,\n ): Promise<Response> {\n const body = await request.text();\n this.logger.debug(\"Linear webhook raw body\", {\n body: body.substring(0, 500),\n });\n\n // Verify request signature (Linear-Signature header)\n // @see https://linear.app/developers/webhooks#securing-webhooks\n const signature = request.headers.get(\"linear-signature\");\n if (!this.verifySignature(body, signature)) {\n return new Response(\"Invalid signature\", { status: 401 });\n }\n\n // Parse the JSON payload\n let payload: LinearWebhookPayload;\n try {\n payload = JSON.parse(body);\n } catch {\n this.logger.error(\"Linear webhook invalid JSON\", {\n contentType: request.headers.get(\"content-type\"),\n bodyPreview: body.substring(0, 200),\n });\n return new Response(\"Invalid JSON\", { status: 400 });\n }\n\n // Validate webhook timestamp to prevent replay attacks (within 5 minutes)\n if (payload.webhookTimestamp) {\n const timeDiff = Math.abs(Date.now() - payload.webhookTimestamp);\n if (timeDiff > 5 * 60 * 1000) {\n this.logger.warn(\"Linear webhook timestamp too old\", {\n webhookTimestamp: payload.webhookTimestamp,\n timeDiff,\n });\n return new Response(\"Webhook expired\", { status: 401 });\n }\n }\n\n // Handle events based on type\n if (payload.type === \"Comment\") {\n const commentPayload = payload as CommentWebhookPayload;\n if (commentPayload.action === \"create\") {\n this.handleCommentCreated(commentPayload, options);\n }\n } else if (payload.type === \"Reaction\") {\n const reactionPayload = payload as ReactionWebhookPayload;\n this.handleReaction(reactionPayload);\n }\n\n return new Response(\"ok\", { status: 200 });\n }\n\n /**\n * Verify Linear webhook signature using HMAC-SHA256.\n *\n * @see https://linear.app/developers/webhooks#securing-webhooks\n */\n private verifySignature(body: string, signature: string | null): boolean {\n if (!signature) {\n return false;\n }\n\n const computedSignature = createHmac(\"sha256\", this.webhookSecret)\n .update(body)\n .digest(\"hex\");\n\n try {\n return timingSafeEqual(\n Buffer.from(computedSignature, \"hex\"),\n Buffer.from(signature, \"hex\"),\n );\n } catch {\n return false;\n }\n }\n\n /**\n * Handle a new comment created on an issue.\n *\n * Threading logic:\n * - If the comment has a parentId, it's a reply -> thread under the parent (root comment)\n * - If no parentId, this is a root comment -> thread under this comment's own ID\n */\n private handleCommentCreated(\n payload: CommentWebhookPayload,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n this.logger.warn(\"Chat instance not initialized, ignoring comment\");\n return;\n }\n\n const { data, actor } = payload;\n\n // Skip if the comment has no issueId (e.g., project update comment)\n if (!data.issueId) {\n this.logger.debug(\"Ignoring non-issue comment\", {\n commentId: data.id,\n });\n return;\n }\n\n // Determine thread: use parentId as root if it's a reply, otherwise this comment is the root\n const rootCommentId = data.parentId || data.id;\n const threadId = this.encodeThreadId({\n issueId: data.issueId,\n commentId: rootCommentId,\n });\n\n // Build message\n const message = this.buildMessage(data, actor, threadId);\n\n // Skip bot's own messages\n if (data.userId === this._botUserId) {\n this.logger.debug(\"Ignoring message from self\", {\n messageId: data.id,\n });\n return;\n }\n\n this.chat.processMessage(this, threadId, message, options);\n }\n\n /**\n * Handle reaction events (logging only - reactions don't include issueId).\n */\n private handleReaction(payload: ReactionWebhookPayload): void {\n if (!this.chat) {\n return;\n }\n\n const { data, actor } = payload;\n\n // Reactions on comments need a commentId to find the thread.\n // Since reaction webhooks don't include issueId directly,\n // we'd need an additional API call to look it up.\n this.logger.debug(\"Received reaction webhook\", {\n reactionId: data.id,\n emoji: data.emoji,\n commentId: data.commentId,\n action: payload.action,\n actorName: actor.name,\n });\n }\n\n /**\n * Build a Message from a Linear comment and actor.\n */\n private buildMessage(\n comment: LinearCommentData,\n actor: LinearWebhookActor,\n threadId: string,\n ): Message<LinearRawMessage> {\n const text = comment.body || \"\";\n\n const author: Author = {\n userId: comment.userId,\n userName: actor.name || \"unknown\",\n fullName: actor.name || \"unknown\",\n isBot: actor.type !== \"user\",\n isMe: comment.userId === this._botUserId,\n };\n\n const formatted: FormattedContent = this.formatConverter.toAst(text);\n\n const raw: LinearRawMessage = {\n comment,\n organizationId: undefined,\n };\n\n return new Message<LinearRawMessage>({\n id: comment.id,\n threadId,\n text,\n formatted,\n raw,\n author,\n metadata: {\n dateSent: comment.createdAt ? new Date(comment.createdAt) : new Date(),\n edited: comment.createdAt !== comment.updatedAt,\n editedAt:\n comment.createdAt !== comment.updatedAt && comment.updatedAt\n ? new Date(comment.updatedAt)\n : undefined,\n },\n attachments: [],\n });\n }\n\n /**\n * Post a message to a thread (create a comment on an issue).\n *\n * For comment-level threads, uses parentId to reply under the root comment.\n * For issue-level threads, creates a top-level comment.\n *\n * Uses LinearClient.createComment({ issueId, body, parentId? }).\n * @see https://linear.app/developers/sdk-fetching-and-modifying-data#mutations\n */\n async postMessage(\n threadId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage<LinearRawMessage>> {\n await this.ensureValidToken();\n const { issueId, commentId } = this.decodeThreadId(threadId);\n\n // Render message to markdown\n let body: string;\n const card = extractCard(message);\n if (card) {\n body = cardToLinearMarkdown(card);\n } else {\n body = this.formatConverter.renderPostable(message);\n }\n\n // Convert emoji placeholders to unicode\n body = convertEmojiPlaceholders(body, \"linear\");\n\n // Create the comment via Linear SDK\n // If commentId is present, reply under that comment (comment-level thread)\n const commentPayload = await this.linearClient.createComment({\n issueId,\n body,\n parentId: commentId,\n });\n\n const comment = await commentPayload.comment;\n if (!comment) {\n throw new Error(\"Failed to create comment on Linear issue\");\n }\n\n return {\n id: comment.id,\n threadId,\n raw: {\n comment: {\n id: comment.id,\n body: comment.body,\n issueId,\n userId: this._botUserId || \"\",\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n },\n },\n };\n }\n\n /**\n * Edit an existing message (update a comment).\n *\n * Uses LinearClient.updateComment(id, { body }).\n * @see https://linear.app/developers/sdk-fetching-and-modifying-data#mutations\n */\n async editMessage(\n threadId: string,\n messageId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage<LinearRawMessage>> {\n await this.ensureValidToken();\n const { issueId } = this.decodeThreadId(threadId);\n\n // Render message to markdown\n let body: string;\n const card = extractCard(message);\n if (card) {\n body = cardToLinearMarkdown(card);\n } else {\n body = this.formatConverter.renderPostable(message);\n }\n\n // Convert emoji placeholders to unicode\n body = convertEmojiPlaceholders(body, \"linear\");\n\n // Update the comment via Linear SDK\n const commentPayload = await this.linearClient.updateComment(messageId, {\n body,\n });\n\n const comment = await commentPayload.comment;\n if (!comment) {\n throw new Error(\"Failed to update comment on Linear\");\n }\n\n return {\n id: comment.id,\n threadId,\n raw: {\n comment: {\n id: comment.id,\n body: comment.body,\n issueId,\n userId: this._botUserId || \"\",\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n },\n },\n };\n }\n\n /**\n * Delete a message (delete a comment).\n *\n * Uses LinearClient.deleteComment(id).\n */\n async deleteMessage(_threadId: string, messageId: string): Promise<void> {\n await this.ensureValidToken();\n await this.linearClient.deleteComment(messageId);\n }\n\n /**\n * Add a reaction to a comment.\n *\n * Uses LinearClient.createReaction({ commentId, emoji }).\n * Linear reactions use emoji strings (unicode).\n */\n async addReaction(\n _threadId: string,\n messageId: string,\n emoji: EmojiValue | string,\n ): Promise<void> {\n await this.ensureValidToken();\n const emojiStr = this.resolveEmoji(emoji);\n await this.linearClient.createReaction({\n commentId: messageId,\n emoji: emojiStr,\n });\n }\n\n /**\n * Remove a reaction from a comment.\n *\n * Linear doesn't have a direct \"remove reaction by emoji + user\" API.\n * Removing requires knowing the reaction ID, which would require fetching\n * the comment's reactions first. This is a known limitation.\n */\n async removeReaction(\n _threadId: string,\n _messageId: string,\n _emoji: EmojiValue | string,\n ): Promise<void> {\n this.logger.warn(\n \"removeReaction is not fully supported on Linear - reaction ID lookup would be required\",\n );\n }\n\n /**\n * Start typing indicator. Not supported by Linear.\n */\n async startTyping(_threadId: string): Promise<void> {\n // Linear doesn't support typing indicators\n }\n\n /**\n * Fetch messages from a thread.\n *\n * For issue-level threads: fetches all top-level issue comments.\n * For comment-level threads: fetches the root comment and its children (replies).\n */\n async fetchMessages(\n threadId: string,\n options?: FetchOptions,\n ): Promise<FetchResult<LinearRawMessage>> {\n await this.ensureValidToken();\n const { issueId, commentId } = this.decodeThreadId(threadId);\n\n if (commentId) {\n // Comment-level thread: fetch root comment's children\n return this.fetchCommentThread(threadId, issueId, commentId, options);\n }\n\n // Issue-level thread: fetch all top-level comments\n return this.fetchIssueComments(threadId, issueId, options);\n }\n\n /**\n * Fetch top-level comments on an issue.\n */\n private async fetchIssueComments(\n threadId: string,\n issueId: string,\n options?: FetchOptions,\n ): Promise<FetchResult<LinearRawMessage>> {\n const issue = await this.linearClient.issue(issueId);\n const commentsConnection = await issue.comments({\n first: options?.limit ?? 50,\n });\n\n const messages = await this.commentsToMessages(\n commentsConnection.nodes,\n threadId,\n issueId,\n );\n\n return {\n messages,\n nextCursor: commentsConnection.pageInfo.hasNextPage\n ? commentsConnection.pageInfo.endCursor\n : undefined,\n };\n }\n\n /**\n * Fetch a comment thread (root comment + its children/replies).\n */\n private async fetchCommentThread(\n threadId: string,\n issueId: string,\n commentId: string,\n options?: FetchOptions,\n ): Promise<FetchResult<LinearRawMessage>> {\n const rootComment = await this.linearClient.comment({ id: commentId });\n if (!rootComment) {\n return { messages: [] };\n }\n\n // Get the children (replies) of the root comment\n const childrenConnection = await rootComment.children({\n first: options?.limit ?? 50,\n });\n\n // Include the root comment as the first message, then its children\n const rootMessages = await this.commentsToMessages(\n [rootComment],\n threadId,\n issueId,\n );\n const childMessages = await this.commentsToMessages(\n childrenConnection.nodes,\n threadId,\n issueId,\n );\n\n return {\n messages: [...rootMessages, ...childMessages],\n nextCursor: childrenConnection.pageInfo.hasNextPage\n ? childrenConnection.pageInfo.endCursor\n : undefined,\n };\n }\n\n /**\n * Convert an array of Linear SDK Comment objects to Message instances.\n */\n private async commentsToMessages(\n comments: Array<{\n id: string;\n body: string;\n createdAt: Date;\n updatedAt: Date;\n url: string;\n user: LinearFetch<User> | undefined;\n }>,\n threadId: string,\n issueId: string,\n ): Promise<Message<LinearRawMessage>[]> {\n const messages: Message<LinearRawMessage>[] = [];\n\n for (const comment of comments) {\n const user = await comment.user;\n const author: Author = {\n userId: user?.id || \"unknown\",\n userName: user?.displayName || \"unknown\",\n fullName: user?.name || user?.displayName || \"unknown\",\n isBot: false,\n isMe: user?.id === this._botUserId,\n };\n\n const formatted: FormattedContent = this.formatConverter.toAst(\n comment.body,\n );\n\n messages.push(\n new Message<LinearRawMessage>({\n id: comment.id,\n threadId,\n text: comment.body,\n formatted,\n author,\n metadata: {\n dateSent: new Date(comment.createdAt),\n edited: comment.createdAt.getTime() !== comment.updatedAt.getTime(),\n editedAt:\n comment.createdAt.getTime() !== comment.updatedAt.getTime()\n ? new Date(comment.updatedAt)\n : undefined,\n },\n attachments: [],\n raw: {\n comment: {\n id: comment.id,\n body: comment.body,\n issueId,\n userId: user?.id || \"unknown\",\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n },\n },\n }),\n );\n }\n\n return messages;\n }\n\n /**\n * Fetch thread info for a Linear issue.\n */\n async fetchThread(threadId: string): Promise<ThreadInfo> {\n await this.ensureValidToken();\n const { issueId } = this.decodeThreadId(threadId);\n\n const issue = await this.linearClient.issue(issueId);\n\n return {\n id: threadId,\n channelId: issueId,\n channelName: `${issue.identifier}: ${issue.title}`,\n isDM: false,\n metadata: {\n issueId,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n },\n };\n }\n\n /**\n * Encode a Linear thread ID.\n *\n * Formats:\n * - Issue-level: linear:{issueId}\n * - Comment thread: linear:{issueId}:c:{commentId}\n */\n encodeThreadId(platformData: LinearThreadId): string {\n if (platformData.commentId) {\n return `linear:${platformData.issueId}:c:${platformData.commentId}`;\n }\n return `linear:${platformData.issueId}`;\n }\n\n /**\n * Decode a Linear thread ID.\n *\n * Formats:\n * - Issue-level: linear:{issueId}\n * - Comment thread: linear:{issueId}:c:{commentId}\n */\n decodeThreadId(threadId: string): LinearThreadId {\n if (!threadId.startsWith(\"linear:\")) {\n throw new ValidationError(\n \"linear\",\n `Invalid Linear thread ID: ${threadId}`,\n );\n }\n\n const withoutPrefix = threadId.slice(7);\n if (!withoutPrefix) {\n throw new ValidationError(\n \"linear\",\n `Invalid Linear thread ID format: ${threadId}`,\n );\n }\n\n // Check for comment thread format: {issueId}:c:{commentId}\n const commentMatch = withoutPrefix.match(/^([^:]+):c:([^:]+)$/);\n if (commentMatch) {\n return {\n issueId: commentMatch[1],\n commentId: commentMatch[2],\n };\n }\n\n // Issue-level format: {issueId}\n return { issueId: withoutPrefix };\n }\n\n /**\n * Parse platform message format to normalized format.\n */\n parseMessage(raw: LinearRawMessage): Message<LinearRawMessage> {\n const text = raw.comment.body || \"\";\n const formatted: FormattedContent = this.formatConverter.toAst(text);\n\n return new Message<LinearRawMessage>({\n id: raw.comment.id,\n threadId: \"\",\n text,\n formatted,\n author: {\n userId: raw.comment.userId,\n userName: \"unknown\",\n fullName: \"unknown\",\n isBot: false,\n isMe: raw.comment.userId === this._botUserId,\n },\n metadata: {\n dateSent: raw.comment.createdAt\n ? new Date(raw.comment.createdAt)\n : new Date(),\n edited: raw.comment.createdAt !== raw.comment.updatedAt,\n editedAt:\n raw.comment.createdAt !== raw.comment.updatedAt &&\n raw.comment.updatedAt\n ? new Date(raw.comment.updatedAt)\n : undefined,\n },\n attachments: [],\n raw,\n });\n }\n\n /**\n * Render formatted content to Linear markdown.\n */\n renderFormatted(content: FormattedContent): string {\n return this.formatConverter.fromAst(content);\n }\n\n /**\n * Resolve an emoji value to a unicode string.\n * Linear uses standard unicode emoji for reactions.\n */\n private resolveEmoji(emoji: EmojiValue | string): string {\n const emojiName = typeof emoji === \"string\" ? emoji : emoji.name;\n\n const mapping: Record<string, string> = {\n thumbs_up: \"\\u{1F44D}\",\n thumbs_down: \"\\u{1F44E}\",\n heart: \"\\u{2764}\\u{FE0F}\",\n fire: \"\\u{1F525}\",\n rocket: \"\\u{1F680}\",\n eyes: \"\\u{1F440}\",\n check: \"\\u{2705}\",\n warning: \"\\u{26A0}\\u{FE0F}\",\n sparkles: \"\\u{2728}\",\n wave: \"\\u{1F44B}\",\n raised_hands: \"\\u{1F64C}\",\n laugh: \"\\u{1F604}\",\n hooray: \"\\u{1F389}\",\n confused: \"\\u{1F615}\",\n };\n\n return mapping[emojiName] || emojiName;\n }\n}\n\n/**\n * Factory function to create a Linear adapter.\n *\n * @example\n * ```typescript\n * const adapter = createLinearAdapter({\n * apiKey: process.env.LINEAR_API_KEY!,\n * webhookSecret: process.env.LINEAR_WEBHOOK_SECRET!,\n * userName: \"my-bot\",\n * logger: console,\n * });\n * ```\n */\nexport function createLinearAdapter(\n config: LinearAdapterConfig,\n): LinearAdapter {\n return new LinearAdapter(config);\n}\n","/**\n * Convert CardElement to Linear-compatible markdown.\n *\n * Since Linear doesn't support rich cards natively, we render cards\n * as clean formatted markdown. Linear comments support standard\n * markdown syntax.\n *\n * @see https://linear.app/docs/comment-on-issues\n */\n\nimport type {\n ActionsElement,\n CardChild,\n CardElement,\n FieldsElement,\n TextElement,\n} from \"chat\";\n\n/**\n * Convert a CardElement to Linear-compatible markdown.\n *\n * Cards are rendered as clean markdown with:\n * - Bold title and subtitle\n * - Text content\n * - Fields as key-value pairs\n * - Buttons as markdown links (action buttons become bold text since Linear has no interactivity)\n *\n * @example\n * ```typescript\n * const card = Card({\n * title: \"Order #1234\",\n * subtitle: \"Status update\",\n * children: [\n * Text(\"Your order has been shipped!\"),\n * Fields([\n * Field({ label: \"Tracking\", value: \"ABC123\" }),\n * ]),\n * Actions([\n * LinkButton({ url: \"https://track.example.com\", label: \"Track Order\" }),\n * ]),\n * ],\n * });\n *\n * // Output:\n * // **Order #1234**\n * // Status update\n * //\n * // Your order has been shipped!\n * //\n * // **Tracking:** ABC123\n * //\n * // [Track Order](https://track.example.com)\n * ```\n */\nexport function cardToLinearMarkdown(card: CardElement): string {\n const lines: string[] = [];\n\n // Title (bold)\n if (card.title) {\n lines.push(`**${escapeMarkdown(card.title)}**`);\n }\n\n // Subtitle\n if (card.subtitle) {\n lines.push(escapeMarkdown(card.subtitle));\n }\n\n // Add spacing after header if there are children\n if ((card.title || card.subtitle) && card.children.length > 0) {\n lines.push(\"\");\n }\n\n // Header image\n if (card.imageUrl) {\n lines.push(``);\n lines.push(\"\");\n }\n\n // Children\n for (let i = 0; i < card.children.length; i++) {\n const child = card.children[i];\n const childLines = renderChild(child);\n\n if (childLines.length > 0) {\n lines.push(...childLines);\n\n // Add spacing between children (except last)\n if (i < card.children.length - 1) {\n lines.push(\"\");\n }\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Render a card child element to markdown lines.\n */\nfunction renderChild(child: CardChild): string[] {\n switch (child.type) {\n case \"text\":\n return renderText(child);\n\n case \"fields\":\n return renderFields(child);\n\n case \"actions\":\n return renderActions(child);\n\n case \"section\":\n // Flatten section children\n return child.children.flatMap(renderChild);\n\n case \"image\":\n if (child.alt) {\n return [``];\n }\n return [``];\n\n case \"divider\":\n return [\"---\"];\n\n default:\n return [];\n }\n}\n\n/**\n * Render text element.\n */\nfunction renderText(text: TextElement): string[] {\n const content = text.content;\n\n switch (text.style) {\n case \"bold\":\n return [`**${content}**`];\n case \"muted\":\n return [`_${content}_`];\n default:\n return [content];\n }\n}\n\n/**\n * Render fields as key-value pairs.\n */\nfunction renderFields(fields: FieldsElement): string[] {\n return fields.children.map(\n (field) =>\n `**${escapeMarkdown(field.label)}:** ${escapeMarkdown(field.value)}`,\n );\n}\n\n/**\n * Render actions (buttons) as markdown links or bold text.\n */\nfunction renderActions(actions: ActionsElement): string[] {\n const buttonTexts = actions.children.map((button) => {\n if (button.type === \"link-button\") {\n return `[${escapeMarkdown(button.label)}](${button.url})`;\n }\n // Action buttons become bold text (no interactivity in Linear comments)\n return `**[${escapeMarkdown(button.label)}]**`;\n });\n\n return [buttonTexts.join(\" \\u2022 \")];\n}\n\n/**\n * Escape special markdown characters in text.\n */\nfunction escapeMarkdown(text: string): string {\n return text\n .replace(/\\*/g, \"\\\\*\")\n .replace(/_/g, \"\\\\_\")\n .replace(/\\[/g, \"\\\\[\")\n .replace(/\\]/g, \"\\\\]\");\n}\n\n/**\n * Generate plain text fallback from a card (no markdown).\n */\nexport function cardToPlainText(card: CardElement): string {\n const parts: string[] = [];\n\n if (card.title) {\n parts.push(card.title);\n }\n\n if (card.subtitle) {\n parts.push(card.subtitle);\n }\n\n for (const child of card.children) {\n const text = childToPlainText(child);\n if (text) {\n parts.push(text);\n }\n }\n\n return parts.join(\"\\n\");\n}\n\n/**\n * Convert card child to plain text.\n */\nfunction childToPlainText(child: CardChild): string | null {\n switch (child.type) {\n case \"text\":\n return child.content;\n case \"fields\":\n return child.children.map((f) => `${f.label}: ${f.value}`).join(\"\\n\");\n case \"actions\":\n return child.children.map((b) => `[${b.label}]`).join(\" \");\n case \"section\":\n return child.children.map(childToPlainText).filter(Boolean).join(\"\\n\");\n default:\n return null;\n }\n}\n","/**\n * Linear-specific format conversion using AST-based parsing.\n *\n * Linear uses standard Markdown for comments, which is very close\n * to the mdast format used by the SDK. This converter is mostly\n * pass-through, similar to the GitHub adapter.\n *\n * @see https://linear.app/docs/comment-on-issues\n */\n\nimport {\n type AdapterPostableMessage,\n BaseFormatConverter,\n parseMarkdown,\n type Root,\n stringifyMarkdown,\n} from \"chat\";\n\nexport class LinearFormatConverter extends BaseFormatConverter {\n /**\n * Convert an AST to standard markdown.\n * Linear uses standard markdown, so we use remark-stringify directly.\n */\n fromAst(ast: Root): string {\n return stringifyMarkdown(ast).trim();\n }\n\n /**\n * Parse markdown into an AST.\n * Linear uses standard markdown, so we use the standard parser.\n */\n toAst(markdown: string): Root {\n return parseMarkdown(markdown);\n }\n\n /**\n * Render a postable message to Linear markdown string.\n */\n override renderPostable(message: AdapterPostableMessage): string {\n if (typeof message === \"string\") {\n return message;\n }\n if (\"raw\" in message) {\n return message.raw;\n }\n if (\"markdown\" in message) {\n return this.fromMarkdown(message.markdown);\n }\n if (\"ast\" in message) {\n return this.fromAst(message.ast);\n }\n // Handle cards via base class\n return super.renderPostable(message);\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/cards.ts","../src/markdown.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { extractCard, ValidationError } from \"@chat-adapter/shared\";\nimport type { LinearFetch, User } from \"@linear/sdk\";\nimport { LinearClient } from \"@linear/sdk\";\nimport type {\n Adapter,\n AdapterPostableMessage,\n Author,\n ChatInstance,\n EmojiValue,\n FetchOptions,\n FetchResult,\n FormattedContent,\n Logger,\n RawMessage,\n ThreadInfo,\n WebhookOptions,\n} from \"chat\";\nimport { convertEmojiPlaceholders, Message } from \"chat\";\nimport { cardToLinearMarkdown } from \"./cards\";\nimport { LinearFormatConverter } from \"./markdown\";\nimport type {\n CommentWebhookPayload,\n LinearAdapterConfig,\n LinearCommentData,\n LinearRawMessage,\n LinearThreadId,\n LinearWebhookActor,\n LinearWebhookPayload,\n ReactionWebhookPayload,\n} from \"./types\";\n\n// Re-export types\nexport type {\n LinearAdapterAPIKeyConfig,\n LinearAdapterAppConfig,\n LinearAdapterConfig,\n LinearAdapterOAuthConfig,\n LinearRawMessage,\n LinearThreadId,\n} from \"./types\";\n\n/**\n * Linear adapter for chat SDK.\n *\n * Supports comment threads on Linear issues.\n * Authentication via personal API key or OAuth access token.\n *\n * @example API Key auth\n * ```typescript\n * import { Chat } from \"chat\";\n * import { createLinearAdapter } from \"@chat-adapter/linear\";\n * import { MemoryState } from \"@chat-adapter/state-memory\";\n *\n * const chat = new Chat({\n * userName: \"my-bot\",\n * adapters: {\n * linear: createLinearAdapter({\n * apiKey: process.env.LINEAR_API_KEY!,\n * webhookSecret: process.env.LINEAR_WEBHOOK_SECRET!,\n * userName: \"my-bot\",\n * logger: console,\n * }),\n * },\n * state: new MemoryState(),\n * logger: \"info\",\n * });\n * ```\n *\n * @example OAuth auth\n * ```typescript\n * const chat = new Chat({\n * userName: \"my-bot\",\n * adapters: {\n * linear: createLinearAdapter({\n * accessToken: process.env.LINEAR_ACCESS_TOKEN!,\n * webhookSecret: process.env.LINEAR_WEBHOOK_SECRET!,\n * userName: \"my-bot\",\n * logger: console,\n * }),\n * },\n * state: new MemoryState(),\n * logger: \"info\",\n * });\n * ```\n */\nexport class LinearAdapter\n implements Adapter<LinearThreadId, LinearRawMessage>\n{\n readonly name = \"linear\";\n readonly userName: string;\n\n private linearClient!: LinearClient;\n private webhookSecret: string;\n private chat: ChatInstance | null = null;\n private logger: Logger;\n private _botUserId: string | null = null;\n private formatConverter = new LinearFormatConverter();\n\n // Client credentials auth state\n private clientCredentials: {\n clientId: string;\n clientSecret: string;\n } | null = null;\n private accessTokenExpiry: number | null = null;\n\n /** Bot user ID used for self-message detection */\n get botUserId(): string | undefined {\n return this._botUserId ?? undefined;\n }\n\n constructor(config: LinearAdapterConfig) {\n this.webhookSecret = config.webhookSecret;\n this.logger = config.logger;\n this.userName = config.userName;\n\n // Create LinearClient based on auth method\n // @see https://linear.app/developers/sdk\n if (\"apiKey\" in config && config.apiKey) {\n this.linearClient = new LinearClient({ apiKey: config.apiKey });\n } else if (\"accessToken\" in config && config.accessToken) {\n this.linearClient = new LinearClient({\n accessToken: config.accessToken,\n });\n } else if (\"clientId\" in config && config.clientId) {\n // Client credentials mode - token will be fetched during initialize()\n this.clientCredentials = {\n clientId: config.clientId,\n clientSecret: config.clientSecret,\n };\n } else {\n throw new Error(\n \"LinearAdapter requires either apiKey, accessToken, or clientId/clientSecret\",\n );\n }\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.chat = chat;\n\n // For client credentials mode, fetch an access token first\n if (this.clientCredentials) {\n await this.refreshClientCredentialsToken();\n }\n\n // Fetch the bot's user ID for self-message detection\n // @see https://linear.app/developers/sdk-fetching-and-modifying-data\n try {\n const viewer = await this.linearClient.viewer;\n this._botUserId = viewer.id;\n this.logger.info(\"Linear auth completed\", {\n botUserId: this._botUserId,\n displayName: viewer.displayName,\n });\n } catch (error) {\n this.logger.warn(\"Could not fetch Linear bot user ID\", { error });\n }\n }\n\n /**\n * Fetch a new access token using client credentials grant.\n * The token is valid for 30 days. The adapter auto-refreshes on 401.\n *\n * @see https://linear.app/developers/oauth-2-0-authentication#client-credentials-tokens\n */\n private async refreshClientCredentialsToken(): Promise<void> {\n if (!this.clientCredentials) return;\n\n const { clientId, clientSecret } = this.clientCredentials;\n\n const response = await fetch(\"https://api.linear.app/oauth/token\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: new URLSearchParams({\n grant_type: \"client_credentials\",\n client_id: clientId,\n client_secret: clientSecret,\n scope: \"read,write,comments:create,issues:create\",\n }),\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n throw new Error(\n `Failed to fetch Linear client credentials token: ${response.status} ${errorBody}`,\n );\n }\n\n const data = (await response.json()) as {\n access_token: string;\n expires_in: number;\n };\n\n this.linearClient = new LinearClient({\n accessToken: data.access_token,\n });\n\n // Track expiry so we can proactively refresh (with 1 hour buffer)\n this.accessTokenExpiry = Date.now() + data.expires_in * 1000 - 3600000;\n\n this.logger.info(\"Linear client credentials token obtained\", {\n expiresIn: `${Math.round(data.expires_in / 86400)} days`,\n });\n }\n\n /**\n * Ensure the client credentials token is still valid. Refresh if expired.\n */\n private async ensureValidToken(): Promise<void> {\n if (\n this.clientCredentials &&\n this.accessTokenExpiry &&\n Date.now() > this.accessTokenExpiry\n ) {\n this.logger.info(\"Linear access token expired, refreshing...\");\n await this.refreshClientCredentialsToken();\n }\n }\n\n /**\n * Handle incoming webhook from Linear.\n *\n * @see https://linear.app/developers/webhooks\n */\n async handleWebhook(\n request: Request,\n options?: WebhookOptions,\n ): Promise<Response> {\n const body = await request.text();\n this.logger.debug(\"Linear webhook raw body\", {\n body: body.substring(0, 500),\n });\n\n // Verify request signature (Linear-Signature header)\n // @see https://linear.app/developers/webhooks#securing-webhooks\n const signature = request.headers.get(\"linear-signature\");\n if (!this.verifySignature(body, signature)) {\n return new Response(\"Invalid signature\", { status: 401 });\n }\n\n // Parse the JSON payload\n let payload: LinearWebhookPayload;\n try {\n payload = JSON.parse(body);\n } catch {\n this.logger.error(\"Linear webhook invalid JSON\", {\n contentType: request.headers.get(\"content-type\"),\n bodyPreview: body.substring(0, 200),\n });\n return new Response(\"Invalid JSON\", { status: 400 });\n }\n\n // Validate webhook timestamp to prevent replay attacks (within 5 minutes)\n if (payload.webhookTimestamp) {\n const timeDiff = Math.abs(Date.now() - payload.webhookTimestamp);\n if (timeDiff > 5 * 60 * 1000) {\n this.logger.warn(\"Linear webhook timestamp too old\", {\n webhookTimestamp: payload.webhookTimestamp,\n timeDiff,\n });\n return new Response(\"Webhook expired\", { status: 401 });\n }\n }\n\n // Handle events based on type\n if (payload.type === \"Comment\") {\n const commentPayload = payload as CommentWebhookPayload;\n if (commentPayload.action === \"create\") {\n this.handleCommentCreated(commentPayload, options);\n }\n } else if (payload.type === \"Reaction\") {\n const reactionPayload = payload as ReactionWebhookPayload;\n this.handleReaction(reactionPayload);\n }\n\n return new Response(\"ok\", { status: 200 });\n }\n\n /**\n * Verify Linear webhook signature using HMAC-SHA256.\n *\n * @see https://linear.app/developers/webhooks#securing-webhooks\n */\n private verifySignature(body: string, signature: string | null): boolean {\n if (!signature) {\n return false;\n }\n\n const computedSignature = createHmac(\"sha256\", this.webhookSecret)\n .update(body)\n .digest(\"hex\");\n\n try {\n return timingSafeEqual(\n Buffer.from(computedSignature, \"hex\"),\n Buffer.from(signature, \"hex\"),\n );\n } catch {\n return false;\n }\n }\n\n /**\n * Handle a new comment created on an issue.\n *\n * Threading logic:\n * - If the comment has a parentId, it's a reply -> thread under the parent (root comment)\n * - If no parentId, this is a root comment -> thread under this comment's own ID\n */\n private handleCommentCreated(\n payload: CommentWebhookPayload,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n this.logger.warn(\"Chat instance not initialized, ignoring comment\");\n return;\n }\n\n const { data, actor } = payload;\n\n // Skip if the comment has no issueId (e.g., project update comment)\n if (!data.issueId) {\n this.logger.debug(\"Ignoring non-issue comment\", {\n commentId: data.id,\n });\n return;\n }\n\n // Determine thread: use parentId as root if it's a reply, otherwise this comment is the root\n const rootCommentId = data.parentId || data.id;\n const threadId = this.encodeThreadId({\n issueId: data.issueId,\n commentId: rootCommentId,\n });\n\n // Build message\n const message = this.buildMessage(data, actor, threadId);\n\n // Skip bot's own messages\n if (data.userId === this._botUserId) {\n this.logger.debug(\"Ignoring message from self\", {\n messageId: data.id,\n });\n return;\n }\n\n this.chat.processMessage(this, threadId, message, options);\n }\n\n /**\n * Handle reaction events (logging only - reactions don't include issueId).\n */\n private handleReaction(payload: ReactionWebhookPayload): void {\n if (!this.chat) {\n return;\n }\n\n const { data, actor } = payload;\n\n // Reactions on comments need a commentId to find the thread.\n // Since reaction webhooks don't include issueId directly,\n // we'd need an additional API call to look it up.\n this.logger.debug(\"Received reaction webhook\", {\n reactionId: data.id,\n emoji: data.emoji,\n commentId: data.commentId,\n action: payload.action,\n actorName: actor.name,\n });\n }\n\n /**\n * Build a Message from a Linear comment and actor.\n */\n private buildMessage(\n comment: LinearCommentData,\n actor: LinearWebhookActor,\n threadId: string,\n ): Message<LinearRawMessage> {\n const text = comment.body || \"\";\n\n const author: Author = {\n userId: comment.userId,\n userName: actor.name || \"unknown\",\n fullName: actor.name || \"unknown\",\n isBot: actor.type !== \"user\",\n isMe: comment.userId === this._botUserId,\n };\n\n const formatted: FormattedContent = this.formatConverter.toAst(text);\n\n const raw: LinearRawMessage = {\n comment,\n organizationId: undefined,\n };\n\n return new Message<LinearRawMessage>({\n id: comment.id,\n threadId,\n text,\n formatted,\n raw,\n author,\n metadata: {\n dateSent: comment.createdAt ? new Date(comment.createdAt) : new Date(),\n edited: comment.createdAt !== comment.updatedAt,\n editedAt:\n comment.createdAt !== comment.updatedAt && comment.updatedAt\n ? new Date(comment.updatedAt)\n : undefined,\n },\n attachments: [],\n });\n }\n\n /**\n * Post a message to a thread (create a comment on an issue).\n *\n * For comment-level threads, uses parentId to reply under the root comment.\n * For issue-level threads, creates a top-level comment.\n *\n * Uses LinearClient.createComment({ issueId, body, parentId? }).\n * @see https://linear.app/developers/sdk-fetching-and-modifying-data#mutations\n */\n async postMessage(\n threadId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage<LinearRawMessage>> {\n await this.ensureValidToken();\n const { issueId, commentId } = this.decodeThreadId(threadId);\n\n // Render message to markdown\n let body: string;\n const card = extractCard(message);\n if (card) {\n body = cardToLinearMarkdown(card);\n } else {\n body = this.formatConverter.renderPostable(message);\n }\n\n // Convert emoji placeholders to unicode\n body = convertEmojiPlaceholders(body, \"linear\");\n\n // Create the comment via Linear SDK\n // If commentId is present, reply under that comment (comment-level thread)\n const commentPayload = await this.linearClient.createComment({\n issueId,\n body,\n parentId: commentId,\n });\n\n const comment = await commentPayload.comment;\n if (!comment) {\n throw new Error(\"Failed to create comment on Linear issue\");\n }\n\n return {\n id: comment.id,\n threadId,\n raw: {\n comment: {\n id: comment.id,\n body: comment.body,\n issueId,\n userId: this._botUserId || \"\",\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n },\n },\n };\n }\n\n /**\n * Edit an existing message (update a comment).\n *\n * Uses LinearClient.updateComment(id, { body }).\n * @see https://linear.app/developers/sdk-fetching-and-modifying-data#mutations\n */\n async editMessage(\n threadId: string,\n messageId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage<LinearRawMessage>> {\n await this.ensureValidToken();\n const { issueId } = this.decodeThreadId(threadId);\n\n // Render message to markdown\n let body: string;\n const card = extractCard(message);\n if (card) {\n body = cardToLinearMarkdown(card);\n } else {\n body = this.formatConverter.renderPostable(message);\n }\n\n // Convert emoji placeholders to unicode\n body = convertEmojiPlaceholders(body, \"linear\");\n\n // Update the comment via Linear SDK\n const commentPayload = await this.linearClient.updateComment(messageId, {\n body,\n });\n\n const comment = await commentPayload.comment;\n if (!comment) {\n throw new Error(\"Failed to update comment on Linear\");\n }\n\n return {\n id: comment.id,\n threadId,\n raw: {\n comment: {\n id: comment.id,\n body: comment.body,\n issueId,\n userId: this._botUserId || \"\",\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n },\n },\n };\n }\n\n /**\n * Delete a message (delete a comment).\n *\n * Uses LinearClient.deleteComment(id).\n */\n async deleteMessage(_threadId: string, messageId: string): Promise<void> {\n await this.ensureValidToken();\n await this.linearClient.deleteComment(messageId);\n }\n\n /**\n * Add a reaction to a comment.\n *\n * Uses LinearClient.createReaction({ commentId, emoji }).\n * Linear reactions use emoji strings (unicode).\n */\n async addReaction(\n _threadId: string,\n messageId: string,\n emoji: EmojiValue | string,\n ): Promise<void> {\n await this.ensureValidToken();\n const emojiStr = this.resolveEmoji(emoji);\n await this.linearClient.createReaction({\n commentId: messageId,\n emoji: emojiStr,\n });\n }\n\n /**\n * Remove a reaction from a comment.\n *\n * Linear doesn't have a direct \"remove reaction by emoji + user\" API.\n * Removing requires knowing the reaction ID, which would require fetching\n * the comment's reactions first. This is a known limitation.\n */\n async removeReaction(\n _threadId: string,\n _messageId: string,\n _emoji: EmojiValue | string,\n ): Promise<void> {\n this.logger.warn(\n \"removeReaction is not fully supported on Linear - reaction ID lookup would be required\",\n );\n }\n\n /**\n * Start typing indicator. Not supported by Linear.\n */\n async startTyping(_threadId: string): Promise<void> {\n // Linear doesn't support typing indicators\n }\n\n /**\n * Fetch messages from a thread.\n *\n * For issue-level threads: fetches all top-level issue comments.\n * For comment-level threads: fetches the root comment and its children (replies).\n */\n async fetchMessages(\n threadId: string,\n options?: FetchOptions,\n ): Promise<FetchResult<LinearRawMessage>> {\n await this.ensureValidToken();\n const { issueId, commentId } = this.decodeThreadId(threadId);\n\n if (commentId) {\n // Comment-level thread: fetch root comment's children\n return this.fetchCommentThread(threadId, issueId, commentId, options);\n }\n\n // Issue-level thread: fetch all top-level comments\n return this.fetchIssueComments(threadId, issueId, options);\n }\n\n /**\n * Fetch top-level comments on an issue.\n */\n private async fetchIssueComments(\n threadId: string,\n issueId: string,\n options?: FetchOptions,\n ): Promise<FetchResult<LinearRawMessage>> {\n const issue = await this.linearClient.issue(issueId);\n const commentsConnection = await issue.comments({\n first: options?.limit ?? 50,\n });\n\n const messages = await this.commentsToMessages(\n commentsConnection.nodes,\n threadId,\n issueId,\n );\n\n return {\n messages,\n nextCursor: commentsConnection.pageInfo.hasNextPage\n ? commentsConnection.pageInfo.endCursor\n : undefined,\n };\n }\n\n /**\n * Fetch a comment thread (root comment + its children/replies).\n */\n private async fetchCommentThread(\n threadId: string,\n issueId: string,\n commentId: string,\n options?: FetchOptions,\n ): Promise<FetchResult<LinearRawMessage>> {\n const rootComment = await this.linearClient.comment({ id: commentId });\n if (!rootComment) {\n return { messages: [] };\n }\n\n // Get the children (replies) of the root comment\n const childrenConnection = await rootComment.children({\n first: options?.limit ?? 50,\n });\n\n // Include the root comment as the first message, then its children\n const rootMessages = await this.commentsToMessages(\n [rootComment],\n threadId,\n issueId,\n );\n const childMessages = await this.commentsToMessages(\n childrenConnection.nodes,\n threadId,\n issueId,\n );\n\n return {\n messages: [...rootMessages, ...childMessages],\n nextCursor: childrenConnection.pageInfo.hasNextPage\n ? childrenConnection.pageInfo.endCursor\n : undefined,\n };\n }\n\n /**\n * Convert an array of Linear SDK Comment objects to Message instances.\n */\n private async commentsToMessages(\n comments: Array<{\n id: string;\n body: string;\n createdAt: Date;\n updatedAt: Date;\n url: string;\n user: LinearFetch<User> | undefined;\n }>,\n threadId: string,\n issueId: string,\n ): Promise<Message<LinearRawMessage>[]> {\n const messages: Message<LinearRawMessage>[] = [];\n\n for (const comment of comments) {\n const user = await comment.user;\n const author: Author = {\n userId: user?.id || \"unknown\",\n userName: user?.displayName || \"unknown\",\n fullName: user?.name || user?.displayName || \"unknown\",\n isBot: false,\n isMe: user?.id === this._botUserId,\n };\n\n const formatted: FormattedContent = this.formatConverter.toAst(\n comment.body,\n );\n\n messages.push(\n new Message<LinearRawMessage>({\n id: comment.id,\n threadId,\n text: comment.body,\n formatted,\n author,\n metadata: {\n dateSent: new Date(comment.createdAt),\n edited: comment.createdAt.getTime() !== comment.updatedAt.getTime(),\n editedAt:\n comment.createdAt.getTime() !== comment.updatedAt.getTime()\n ? new Date(comment.updatedAt)\n : undefined,\n },\n attachments: [],\n raw: {\n comment: {\n id: comment.id,\n body: comment.body,\n issueId,\n userId: user?.id || \"unknown\",\n createdAt: comment.createdAt.toISOString(),\n updatedAt: comment.updatedAt.toISOString(),\n url: comment.url,\n },\n },\n }),\n );\n }\n\n return messages;\n }\n\n /**\n * Fetch thread info for a Linear issue.\n */\n async fetchThread(threadId: string): Promise<ThreadInfo> {\n await this.ensureValidToken();\n const { issueId } = this.decodeThreadId(threadId);\n\n const issue = await this.linearClient.issue(issueId);\n\n return {\n id: threadId,\n channelId: issueId,\n channelName: `${issue.identifier}: ${issue.title}`,\n isDM: false,\n metadata: {\n issueId,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n },\n };\n }\n\n /**\n * Encode a Linear thread ID.\n *\n * Formats:\n * - Issue-level: linear:{issueId}\n * - Comment thread: linear:{issueId}:c:{commentId}\n */\n encodeThreadId(platformData: LinearThreadId): string {\n if (platformData.commentId) {\n return `linear:${platformData.issueId}:c:${platformData.commentId}`;\n }\n return `linear:${platformData.issueId}`;\n }\n\n /**\n * Decode a Linear thread ID.\n *\n * Formats:\n * - Issue-level: linear:{issueId}\n * - Comment thread: linear:{issueId}:c:{commentId}\n */\n decodeThreadId(threadId: string): LinearThreadId {\n if (!threadId.startsWith(\"linear:\")) {\n throw new ValidationError(\n \"linear\",\n `Invalid Linear thread ID: ${threadId}`,\n );\n }\n\n const withoutPrefix = threadId.slice(7);\n if (!withoutPrefix) {\n throw new ValidationError(\n \"linear\",\n `Invalid Linear thread ID format: ${threadId}`,\n );\n }\n\n // Check for comment thread format: {issueId}:c:{commentId}\n const commentMatch = withoutPrefix.match(/^([^:]+):c:([^:]+)$/);\n if (commentMatch) {\n return {\n issueId: commentMatch[1],\n commentId: commentMatch[2],\n };\n }\n\n // Issue-level format: {issueId}\n return { issueId: withoutPrefix };\n }\n\n /**\n * Parse platform message format to normalized format.\n */\n parseMessage(raw: LinearRawMessage): Message<LinearRawMessage> {\n const text = raw.comment.body || \"\";\n const formatted: FormattedContent = this.formatConverter.toAst(text);\n\n return new Message<LinearRawMessage>({\n id: raw.comment.id,\n threadId: \"\",\n text,\n formatted,\n author: {\n userId: raw.comment.userId,\n userName: \"unknown\",\n fullName: \"unknown\",\n isBot: false,\n isMe: raw.comment.userId === this._botUserId,\n },\n metadata: {\n dateSent: raw.comment.createdAt\n ? new Date(raw.comment.createdAt)\n : new Date(),\n edited: raw.comment.createdAt !== raw.comment.updatedAt,\n editedAt:\n raw.comment.createdAt !== raw.comment.updatedAt &&\n raw.comment.updatedAt\n ? new Date(raw.comment.updatedAt)\n : undefined,\n },\n attachments: [],\n raw,\n });\n }\n\n /**\n * Render formatted content to Linear markdown.\n */\n renderFormatted(content: FormattedContent): string {\n return this.formatConverter.fromAst(content);\n }\n\n /**\n * Resolve an emoji value to a unicode string.\n * Linear uses standard unicode emoji for reactions.\n */\n private resolveEmoji(emoji: EmojiValue | string): string {\n const emojiName = typeof emoji === \"string\" ? emoji : emoji.name;\n\n const mapping: Record<string, string> = {\n thumbs_up: \"\\u{1F44D}\",\n thumbs_down: \"\\u{1F44E}\",\n heart: \"\\u{2764}\\u{FE0F}\",\n fire: \"\\u{1F525}\",\n rocket: \"\\u{1F680}\",\n eyes: \"\\u{1F440}\",\n check: \"\\u{2705}\",\n warning: \"\\u{26A0}\\u{FE0F}\",\n sparkles: \"\\u{2728}\",\n wave: \"\\u{1F44B}\",\n raised_hands: \"\\u{1F64C}\",\n laugh: \"\\u{1F604}\",\n hooray: \"\\u{1F389}\",\n confused: \"\\u{1F615}\",\n };\n\n return mapping[emojiName] || emojiName;\n }\n}\n\n/**\n * Factory function to create a Linear adapter.\n *\n * @example\n * ```typescript\n * const adapter = createLinearAdapter({\n * apiKey: process.env.LINEAR_API_KEY!,\n * webhookSecret: process.env.LINEAR_WEBHOOK_SECRET!,\n * userName: \"my-bot\",\n * logger: console,\n * });\n * ```\n */\nexport function createLinearAdapter(\n config: LinearAdapterConfig,\n): LinearAdapter {\n return new LinearAdapter(config);\n}\n","/**\n * Convert CardElement to Linear-compatible markdown.\n *\n * Since Linear doesn't support rich cards natively, we render cards\n * as clean formatted markdown. Linear comments support standard\n * markdown syntax.\n *\n * @see https://linear.app/docs/comment-on-issues\n */\n\nimport type {\n ActionsElement,\n CardChild,\n CardElement,\n FieldsElement,\n TextElement,\n} from \"chat\";\n\n/**\n * Convert a CardElement to Linear-compatible markdown.\n *\n * Cards are rendered as clean markdown with:\n * - Bold title and subtitle\n * - Text content\n * - Fields as key-value pairs\n * - Buttons as markdown links (action buttons become bold text since Linear has no interactivity)\n *\n * @example\n * ```typescript\n * const card = Card({\n * title: \"Order #1234\",\n * subtitle: \"Status update\",\n * children: [\n * Text(\"Your order has been shipped!\"),\n * Fields([\n * Field({ label: \"Tracking\", value: \"ABC123\" }),\n * ]),\n * Actions([\n * LinkButton({ url: \"https://track.example.com\", label: \"Track Order\" }),\n * ]),\n * ],\n * });\n *\n * // Output:\n * // **Order #1234**\n * // Status update\n * //\n * // Your order has been shipped!\n * //\n * // **Tracking:** ABC123\n * //\n * // [Track Order](https://track.example.com)\n * ```\n */\nexport function cardToLinearMarkdown(card: CardElement): string {\n const lines: string[] = [];\n\n // Title (bold)\n if (card.title) {\n lines.push(`**${escapeMarkdown(card.title)}**`);\n }\n\n // Subtitle\n if (card.subtitle) {\n lines.push(escapeMarkdown(card.subtitle));\n }\n\n // Add spacing after header if there are children\n if ((card.title || card.subtitle) && card.children.length > 0) {\n lines.push(\"\");\n }\n\n // Header image\n if (card.imageUrl) {\n lines.push(``);\n lines.push(\"\");\n }\n\n // Children\n for (let i = 0; i < card.children.length; i++) {\n const child = card.children[i];\n const childLines = renderChild(child);\n\n if (childLines.length > 0) {\n lines.push(...childLines);\n\n // Add spacing between children (except last)\n if (i < card.children.length - 1) {\n lines.push(\"\");\n }\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Render a card child element to markdown lines.\n */\nfunction renderChild(child: CardChild): string[] {\n switch (child.type) {\n case \"text\":\n return renderText(child);\n\n case \"fields\":\n return renderFields(child);\n\n case \"actions\":\n return renderActions(child);\n\n case \"section\":\n // Flatten section children\n return child.children.flatMap(renderChild);\n\n case \"image\":\n if (child.alt) {\n return [``];\n }\n return [``];\n\n case \"divider\":\n return [\"---\"];\n\n default:\n return [];\n }\n}\n\n/**\n * Render text element.\n */\nfunction renderText(text: TextElement): string[] {\n const content = text.content;\n\n switch (text.style) {\n case \"bold\":\n return [`**${content}**`];\n case \"muted\":\n return [`_${content}_`];\n default:\n return [content];\n }\n}\n\n/**\n * Render fields as key-value pairs.\n */\nfunction renderFields(fields: FieldsElement): string[] {\n return fields.children.map(\n (field) =>\n `**${escapeMarkdown(field.label)}:** ${escapeMarkdown(field.value)}`,\n );\n}\n\n/**\n * Render actions (buttons) as markdown links or bold text.\n */\nfunction renderActions(actions: ActionsElement): string[] {\n const buttonTexts = actions.children.map((button) => {\n if (button.type === \"link-button\") {\n return `[${escapeMarkdown(button.label)}](${button.url})`;\n }\n // Action buttons become bold text (no interactivity in Linear comments)\n return `**[${escapeMarkdown(button.label)}]**`;\n });\n\n return [buttonTexts.join(\" \\u2022 \")];\n}\n\n/**\n * Escape special markdown characters in text.\n */\nfunction escapeMarkdown(text: string): string {\n return text\n .replace(/\\*/g, \"\\\\*\")\n .replace(/_/g, \"\\\\_\")\n .replace(/\\[/g, \"\\\\[\")\n .replace(/\\]/g, \"\\\\]\");\n}\n\n/**\n * Generate plain text fallback from a card (no markdown).\n */\nexport function cardToPlainText(card: CardElement): string {\n const parts: string[] = [];\n\n if (card.title) {\n parts.push(card.title);\n }\n\n if (card.subtitle) {\n parts.push(card.subtitle);\n }\n\n for (const child of card.children) {\n const text = childToPlainText(child);\n if (text) {\n parts.push(text);\n }\n }\n\n return parts.join(\"\\n\");\n}\n\n/**\n * Convert card child to plain text.\n */\nfunction childToPlainText(child: CardChild): string | null {\n switch (child.type) {\n case \"text\":\n return child.content;\n case \"fields\":\n return child.children.map((f) => `${f.label}: ${f.value}`).join(\"\\n\");\n case \"actions\":\n // Actions are interactive-only — exclude from fallback text.\n // See: https://docs.slack.dev/reference/methods/chat.postMessage\n return null;\n case \"section\":\n return child.children.map(childToPlainText).filter(Boolean).join(\"\\n\");\n default:\n return null;\n }\n}\n","/**\n * Linear-specific format conversion using AST-based parsing.\n *\n * Linear uses standard Markdown for comments, which is very close\n * to the mdast format used by the SDK. This converter is mostly\n * pass-through, similar to the GitHub adapter.\n *\n * @see https://linear.app/docs/comment-on-issues\n */\n\nimport {\n type AdapterPostableMessage,\n BaseFormatConverter,\n parseMarkdown,\n type Root,\n stringifyMarkdown,\n} from \"chat\";\n\nexport class LinearFormatConverter extends BaseFormatConverter {\n /**\n * Convert an AST to standard markdown.\n * Linear uses standard markdown, so we use remark-stringify directly.\n */\n fromAst(ast: Root): string {\n return stringifyMarkdown(ast).trim();\n }\n\n /**\n * Parse markdown into an AST.\n * Linear uses standard markdown, so we use the standard parser.\n */\n toAst(markdown: string): Root {\n return parseMarkdown(markdown);\n }\n\n /**\n * Render a postable message to Linear markdown string.\n */\n override renderPostable(message: AdapterPostableMessage): string {\n if (typeof message === \"string\") {\n return message;\n }\n if (\"raw\" in message) {\n return message.raw;\n }\n if (\"markdown\" in message) {\n return this.fromMarkdown(message.markdown);\n }\n if (\"ast\" in message) {\n return this.fromAst(message.ast);\n }\n // Handle cards via base class\n return super.renderPostable(message);\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chat-adapter/linear",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.1",
|
|
4
4
|
"description": "Linear adapter for chat - issue comment threads",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@linear/sdk": "^37.0.0",
|
|
20
|
-
"@chat-adapter/shared": "4.9.
|
|
21
|
-
"chat": "4.9.
|
|
20
|
+
"@chat-adapter/shared": "4.9.1",
|
|
21
|
+
"chat": "4.9.1"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^22.10.2",
|