@chat-adapter/linear 4.16.0 → 4.17.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.
package/dist/index.d.ts CHANGED
@@ -11,19 +11,21 @@ import { Logger, Adapter, ChatInstance, WebhookOptions, AdapterPostableMessage,
11
11
  * Base configuration options shared by all auth methods.
12
12
  */
13
13
  interface LinearAdapterBaseConfig {
14
- /** Logger instance for error reporting */
15
- logger: Logger;
14
+ /** Logger instance for error reporting. Defaults to ConsoleLogger. */
15
+ logger?: Logger;
16
16
  /**
17
17
  * Bot display name used for @-mention detection.
18
18
  * For API key auth, this is typically the user's display name.
19
19
  * For OAuth app auth with actor=app, this is the app name.
20
+ * Defaults to LINEAR_BOT_USERNAME env var or "linear-bot".
20
21
  */
21
- userName: string;
22
+ userName?: string;
22
23
  /**
23
24
  * Webhook signing secret for HMAC-SHA256 verification.
24
25
  * Found on the webhook detail page in Linear settings.
26
+ * Defaults to LINEAR_WEBHOOK_SECRET env var.
25
27
  */
26
- webhookSecret: string;
28
+ webhookSecret?: string;
27
29
  }
28
30
  /**
29
31
  * Configuration using a personal API key.
@@ -33,8 +35,10 @@ interface LinearAdapterBaseConfig {
33
35
  */
34
36
  interface LinearAdapterAPIKeyConfig extends LinearAdapterBaseConfig {
35
37
  accessToken?: never;
36
- /** Personal API key from Linear Settings > Security & Access */
38
+ /** Personal API key from Linear Settings > Security & Access. Defaults to LINEAR_API_KEY env var. */
37
39
  apiKey: string;
40
+ clientId?: never;
41
+ clientSecret?: never;
38
42
  }
39
43
  /**
40
44
  * Configuration using an OAuth access token (pre-obtained).
@@ -43,7 +47,7 @@ interface LinearAdapterAPIKeyConfig extends LinearAdapterBaseConfig {
43
47
  * @see https://linear.app/developers/oauth-2-0-authentication
44
48
  */
45
49
  interface LinearAdapterOAuthConfig extends LinearAdapterBaseConfig {
46
- /** OAuth access token obtained through the OAuth flow */
50
+ /** OAuth access token obtained through the OAuth flow. Defaults to LINEAR_ACCESS_TOKEN env var. */
47
51
  accessToken: string;
48
52
  apiKey?: never;
49
53
  clientId?: never;
@@ -61,15 +65,24 @@ interface LinearAdapterOAuthConfig extends LinearAdapterBaseConfig {
61
65
  interface LinearAdapterAppConfig extends LinearAdapterBaseConfig {
62
66
  accessToken?: never;
63
67
  apiKey?: never;
64
- /** OAuth application client ID */
68
+ /** OAuth application client ID. Defaults to LINEAR_CLIENT_ID env var. */
65
69
  clientId: string;
66
- /** OAuth application client secret */
70
+ /** OAuth application client secret. Defaults to LINEAR_CLIENT_SECRET env var. */
67
71
  clientSecret: string;
68
72
  }
73
+ /**
74
+ * Configuration with no auth fields - will auto-detect from env vars.
75
+ */
76
+ interface LinearAdapterAutoConfig extends LinearAdapterBaseConfig {
77
+ accessToken?: never;
78
+ apiKey?: never;
79
+ clientId?: never;
80
+ clientSecret?: never;
81
+ }
69
82
  /**
70
83
  * Linear adapter configuration - API Key, OAuth token, or OAuth App (client credentials).
71
84
  */
72
- type LinearAdapterConfig = LinearAdapterAPIKeyConfig | LinearAdapterOAuthConfig | LinearAdapterAppConfig;
85
+ type LinearAdapterConfig = LinearAdapterAPIKeyConfig | LinearAdapterOAuthConfig | LinearAdapterAppConfig | LinearAdapterAutoConfig;
73
86
  /**
74
87
  * Decoded thread ID for Linear.
75
88
  *
@@ -180,7 +193,7 @@ declare class LinearAdapter implements Adapter<LinearThreadId, LinearRawMessage>
180
193
  private accessTokenExpiry;
181
194
  /** Bot user ID used for self-message detection */
182
195
  get botUserId(): string | undefined;
183
- constructor(config: LinearAdapterConfig);
196
+ constructor(config?: LinearAdapterConfig);
184
197
  initialize(chat: ChatInstance): Promise<void>;
185
198
  /**
186
199
  * Fetch a new access token using client credentials grant.
@@ -335,14 +348,6 @@ declare class LinearAdapter implements Adapter<LinearThreadId, LinearRawMessage>
335
348
  * });
336
349
  * ```
337
350
  */
338
- declare function createLinearAdapter(config?: {
339
- accessToken?: string;
340
- apiKey?: string;
341
- clientId?: string;
342
- clientSecret?: string;
343
- logger?: Logger;
344
- userName?: string;
345
- webhookSecret?: string;
346
- }): LinearAdapter;
351
+ declare function createLinearAdapter(config?: LinearAdapterConfig): LinearAdapter;
347
352
 
348
353
  export { LinearAdapter, type LinearAdapterAPIKeyConfig, type LinearAdapterAppConfig, type LinearAdapterConfig, type LinearAdapterOAuthConfig, type LinearRawMessage, type LinearThreadId, createLinearAdapter };
package/dist/index.js CHANGED
@@ -155,10 +155,17 @@ var LinearAdapter = class {
155
155
  get botUserId() {
156
156
  return this._botUserId ?? void 0;
157
157
  }
158
- constructor(config) {
159
- this.webhookSecret = config.webhookSecret;
160
- this.logger = config.logger;
161
- this.userName = config.userName;
158
+ constructor(config = {}) {
159
+ const webhookSecret = config.webhookSecret ?? process.env.LINEAR_WEBHOOK_SECRET;
160
+ if (!webhookSecret) {
161
+ throw new ValidationError(
162
+ "linear",
163
+ "webhookSecret is required. Set LINEAR_WEBHOOK_SECRET or provide it in config."
164
+ );
165
+ }
166
+ this.webhookSecret = webhookSecret;
167
+ this.logger = config.logger ?? new ConsoleLogger("info").child("linear");
168
+ this.userName = config.userName ?? process.env.LINEAR_BOT_USERNAME ?? "linear-bot";
162
169
  if ("apiKey" in config && config.apiKey) {
163
170
  this.linearClient = new LinearClient({ apiKey: config.apiKey });
164
171
  } else if ("accessToken" in config && config.accessToken) {
@@ -171,9 +178,26 @@ var LinearAdapter = class {
171
178
  clientSecret: config.clientSecret
172
179
  };
173
180
  } else {
174
- throw new Error(
175
- "LinearAdapter requires either apiKey, accessToken, or clientId/clientSecret"
176
- );
181
+ const apiKey = process.env.LINEAR_API_KEY;
182
+ if (apiKey) {
183
+ this.linearClient = new LinearClient({ apiKey });
184
+ } else {
185
+ const accessToken = process.env.LINEAR_ACCESS_TOKEN;
186
+ if (accessToken) {
187
+ this.linearClient = new LinearClient({ accessToken });
188
+ } else {
189
+ const clientId = process.env.LINEAR_CLIENT_ID;
190
+ const clientSecret = process.env.LINEAR_CLIENT_SECRET;
191
+ if (clientId && clientSecret) {
192
+ this.clientCredentials = { clientId, clientSecret };
193
+ } else {
194
+ throw new ValidationError(
195
+ "linear",
196
+ "Authentication is required. Set LINEAR_API_KEY, LINEAR_ACCESS_TOKEN, or LINEAR_CLIENT_ID/LINEAR_CLIENT_SECRET, or provide auth in config."
197
+ );
198
+ }
199
+ }
200
+ }
177
201
  }
178
202
  }
179
203
  async initialize(chat) {
@@ -744,49 +768,7 @@ var LinearAdapter = class {
744
768
  }
745
769
  };
746
770
  function createLinearAdapter(config) {
747
- const logger = config?.logger ?? new ConsoleLogger("info").child("linear");
748
- const webhookSecret = config?.webhookSecret ?? process.env.LINEAR_WEBHOOK_SECRET;
749
- if (!webhookSecret) {
750
- throw new ValidationError(
751
- "linear",
752
- "webhookSecret is required. Set LINEAR_WEBHOOK_SECRET or provide it in config."
753
- );
754
- }
755
- const userName = config?.userName ?? process.env.LINEAR_BOT_USERNAME ?? "linear-bot";
756
- const hasAuthConfig = !!(config?.apiKey || config?.accessToken || config?.clientId || config?.clientSecret);
757
- const apiKey = config?.apiKey ?? (hasAuthConfig ? void 0 : process.env.LINEAR_API_KEY);
758
- if (apiKey) {
759
- return new LinearAdapter({
760
- apiKey,
761
- webhookSecret,
762
- userName,
763
- logger
764
- });
765
- }
766
- const accessToken = config?.accessToken ?? (hasAuthConfig ? void 0 : process.env.LINEAR_ACCESS_TOKEN);
767
- if (accessToken) {
768
- return new LinearAdapter({
769
- accessToken,
770
- webhookSecret,
771
- userName,
772
- logger
773
- });
774
- }
775
- const clientId = config?.clientId ?? (hasAuthConfig ? void 0 : process.env.LINEAR_CLIENT_ID);
776
- const clientSecret = config?.clientSecret ?? (hasAuthConfig ? void 0 : process.env.LINEAR_CLIENT_SECRET);
777
- if (clientId && clientSecret) {
778
- return new LinearAdapter({
779
- clientId,
780
- clientSecret,
781
- webhookSecret,
782
- userName,
783
- logger
784
- });
785
- }
786
- throw new ValidationError(
787
- "linear",
788
- "Authentication is required. Set LINEAR_API_KEY, LINEAR_ACCESS_TOKEN, or LINEAR_CLIENT_ID/LINEAR_CLIENT_SECRET, or provide auth in config."
789
- );
771
+ return new LinearAdapter(config);
790
772
  }
791
773
  export {
792
774
  LinearAdapter,
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 { ConsoleLogger, 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\nconst COMMENT_THREAD_PATTERN = /^([^:]+):c:([^:]+)$/;\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 readonly webhookSecret: string;\n private chat: ChatInstance | null = null;\n private readonly logger: Logger;\n private _botUserId: string | null = null;\n private readonly formatConverter = new LinearFormatConverter();\n\n // Client credentials auth state\n private readonly 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) {\n return;\n }\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, _status?: 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 ?? undefined)\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 ?? undefined)\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(COMMENT_THREAD_PATTERN);\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 * Derive channel ID from a Linear thread ID.\n * linear:{issueId}:c:{commentId} -> linear:{issueId}\n * linear:{issueId} -> linear:{issueId}\n */\n channelIdFromThreadId(threadId: string): string {\n const { issueId } = this.decodeThreadId(threadId);\n return `linear:${issueId}`;\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(config?: {\n accessToken?: string;\n apiKey?: string;\n clientId?: string;\n clientSecret?: string;\n logger?: Logger;\n userName?: string;\n webhookSecret?: string;\n}): LinearAdapter {\n const logger = config?.logger ?? new ConsoleLogger(\"info\").child(\"linear\");\n const webhookSecret =\n config?.webhookSecret ?? process.env.LINEAR_WEBHOOK_SECRET;\n if (!webhookSecret) {\n throw new ValidationError(\n \"linear\",\n \"webhookSecret is required. Set LINEAR_WEBHOOK_SECRET or provide it in config.\"\n );\n }\n const userName =\n config?.userName ?? process.env.LINEAR_BOT_USERNAME ?? \"linear-bot\";\n\n // Auto-detect auth mode. Only fall back to env vars for auth fields when\n // the caller hasn't provided ANY auth field, so we don't mix auth modes.\n const hasAuthConfig = !!(\n config?.apiKey ||\n config?.accessToken ||\n config?.clientId ||\n config?.clientSecret\n );\n\n const apiKey =\n config?.apiKey ?? (hasAuthConfig ? undefined : process.env.LINEAR_API_KEY);\n if (apiKey) {\n return new LinearAdapter({\n apiKey,\n webhookSecret,\n userName,\n logger,\n });\n }\n\n const accessToken =\n config?.accessToken ??\n (hasAuthConfig ? undefined : process.env.LINEAR_ACCESS_TOKEN);\n if (accessToken) {\n return new LinearAdapter({\n accessToken,\n webhookSecret,\n userName,\n logger,\n });\n }\n\n const clientId =\n config?.clientId ??\n (hasAuthConfig ? undefined : process.env.LINEAR_CLIENT_ID);\n const clientSecret =\n config?.clientSecret ??\n (hasAuthConfig ? undefined : process.env.LINEAR_CLIENT_SECRET);\n if (clientId && clientSecret) {\n return new LinearAdapter({\n clientId,\n clientSecret,\n webhookSecret,\n userName,\n logger,\n });\n }\n\n throw new ValidationError(\n \"linear\",\n \"Authentication is required. Set LINEAR_API_KEY, LINEAR_ACCESS_TOKEN, or LINEAR_CLIENT_ID/LINEAR_CLIENT_SECRET, or provide auth in config.\"\n );\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 { renderGfmTable } from \"@chat-adapter/shared\";\nimport type {\n ActionsElement,\n CardChild,\n CardElement,\n FieldsElement,\n TableElement,\n TextElement,\n} from \"chat\";\nimport { cardChildToFallbackText } 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(`![](${card.imageUrl})`);\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 [`![${escapeMarkdown(child.alt)}](${child.url})`];\n }\n return [`![](${child.url})`];\n\n case \"link\":\n return [`[${escapeMarkdown(child.label)}](${child.url})`];\n\n case \"divider\":\n return [\"---\"];\n\n case \"table\":\n return renderTable(child);\n\n default: {\n const text = cardChildToFallbackText(child);\n if (text) {\n return [text];\n }\n return [];\n }\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 table as GFM markdown table.\n */\nfunction renderTable(table: TableElement): string[] {\n return renderGfmTable(table);\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 \"table\":\n return renderTable(child).join(\"\\n\");\n case \"section\":\n return child.children.map(childToPlainText).filter(Boolean).join(\"\\n\");\n default:\n return cardChildToFallbackText(child);\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 { ConsoleLogger, convertEmojiPlaceholders, Message } from \"chat\";\nimport { cardToLinearMarkdown } from \"./cards\";\nimport { LinearFormatConverter } from \"./markdown\";\nimport type {\n CommentWebhookPayload,\n LinearAdapterAutoConfig,\n LinearAdapterConfig,\n LinearCommentData,\n LinearRawMessage,\n LinearThreadId,\n LinearWebhookActor,\n LinearWebhookPayload,\n ReactionWebhookPayload,\n} from \"./types\";\n\nconst COMMENT_THREAD_PATTERN = /^([^:]+):c:([^:]+)$/;\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 readonly webhookSecret: string;\n private chat: ChatInstance | null = null;\n private readonly logger: Logger;\n private _botUserId: string | null = null;\n private readonly formatConverter = new LinearFormatConverter();\n\n // Client credentials auth state\n private readonly 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 = {} as LinearAdapterAutoConfig) {\n const webhookSecret =\n config.webhookSecret ?? process.env.LINEAR_WEBHOOK_SECRET;\n if (!webhookSecret) {\n throw new ValidationError(\n \"linear\",\n \"webhookSecret is required. Set LINEAR_WEBHOOK_SECRET or provide it in config.\"\n );\n }\n this.webhookSecret = webhookSecret;\n this.logger = config.logger ?? new ConsoleLogger(\"info\").child(\"linear\");\n this.userName =\n config.userName ?? process.env.LINEAR_BOT_USERNAME ?? \"linear-bot\";\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 // Auto-detect from env vars\n const apiKey = process.env.LINEAR_API_KEY;\n if (apiKey) {\n this.linearClient = new LinearClient({ apiKey });\n } else {\n const accessToken = process.env.LINEAR_ACCESS_TOKEN;\n if (accessToken) {\n this.linearClient = new LinearClient({ accessToken });\n } else {\n const clientId = process.env.LINEAR_CLIENT_ID;\n const clientSecret = process.env.LINEAR_CLIENT_SECRET;\n if (clientId && clientSecret) {\n this.clientCredentials = { clientId, clientSecret };\n } else {\n throw new ValidationError(\n \"linear\",\n \"Authentication is required. Set LINEAR_API_KEY, LINEAR_ACCESS_TOKEN, or LINEAR_CLIENT_ID/LINEAR_CLIENT_SECRET, or provide auth in config.\"\n );\n }\n }\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) {\n return;\n }\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, _status?: 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 ?? undefined)\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 ?? undefined)\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(COMMENT_THREAD_PATTERN);\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 * Derive channel ID from a Linear thread ID.\n * linear:{issueId}:c:{commentId} -> linear:{issueId}\n * linear:{issueId} -> linear:{issueId}\n */\n channelIdFromThreadId(threadId: string): string {\n const { issueId } = this.decodeThreadId(threadId);\n return `linear:${issueId}`;\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 { renderGfmTable } from \"@chat-adapter/shared\";\nimport type {\n ActionsElement,\n CardChild,\n CardElement,\n FieldsElement,\n TableElement,\n TextElement,\n} from \"chat\";\nimport { cardChildToFallbackText } 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(`![](${card.imageUrl})`);\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 [`![${escapeMarkdown(child.alt)}](${child.url})`];\n }\n return [`![](${child.url})`];\n\n case \"link\":\n return [`[${escapeMarkdown(child.label)}](${child.url})`];\n\n case \"divider\":\n return [\"---\"];\n\n case \"table\":\n return renderTable(child);\n\n default: {\n const text = cardChildToFallbackText(child);\n if (text) {\n return [text];\n }\n return [];\n }\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 table as GFM markdown table.\n */\nfunction renderTable(table: TableElement): string[] {\n return renderGfmTable(table);\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 \"table\":\n return renderTable(child).join(\"\\n\");\n case \"section\":\n return child.children.map(childToPlainText).filter(Boolean).join(\"\\n\");\n default:\n return cardChildToFallbackText(child);\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.16.0",
3
+ "version": "4.17.0",
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": "^76.0.0",
20
- "@chat-adapter/shared": "4.16.0",
21
- "chat": "4.16.0"
20
+ "@chat-adapter/shared": "4.17.0",
21
+ "chat": "4.17.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/node": "^25.3.2",