@stackmemoryai/stackmemory 0.5.29 → 0.5.31

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.
Files changed (42) hide show
  1. package/README.md +53 -32
  2. package/dist/core/database/batch-operations.js +29 -4
  3. package/dist/core/database/batch-operations.js.map +2 -2
  4. package/dist/core/database/connection-pool.js +13 -2
  5. package/dist/core/database/connection-pool.js.map +2 -2
  6. package/dist/core/database/migration-manager.js +130 -34
  7. package/dist/core/database/migration-manager.js.map +2 -2
  8. package/dist/core/database/paradedb-adapter.js +23 -7
  9. package/dist/core/database/paradedb-adapter.js.map +2 -2
  10. package/dist/core/database/query-router.js +8 -3
  11. package/dist/core/database/query-router.js.map +2 -2
  12. package/dist/core/database/sqlite-adapter.js +152 -33
  13. package/dist/core/database/sqlite-adapter.js.map +2 -2
  14. package/dist/integrations/linear/auth.js +34 -20
  15. package/dist/integrations/linear/auth.js.map +2 -2
  16. package/dist/integrations/linear/auto-sync.js +18 -8
  17. package/dist/integrations/linear/auto-sync.js.map +2 -2
  18. package/dist/integrations/linear/client.js +42 -9
  19. package/dist/integrations/linear/client.js.map +2 -2
  20. package/dist/integrations/linear/migration.js +94 -36
  21. package/dist/integrations/linear/migration.js.map +2 -2
  22. package/dist/integrations/linear/oauth-server.js +77 -34
  23. package/dist/integrations/linear/oauth-server.js.map +2 -2
  24. package/dist/integrations/linear/rest-client.js +13 -3
  25. package/dist/integrations/linear/rest-client.js.map +2 -2
  26. package/dist/integrations/linear/sync-service.js +18 -15
  27. package/dist/integrations/linear/sync-service.js.map +2 -2
  28. package/dist/integrations/linear/sync.js +12 -4
  29. package/dist/integrations/linear/sync.js.map +2 -2
  30. package/dist/integrations/linear/unified-sync.js +33 -8
  31. package/dist/integrations/linear/unified-sync.js.map +2 -2
  32. package/dist/integrations/linear/webhook-handler.js +5 -1
  33. package/dist/integrations/linear/webhook-handler.js.map +2 -2
  34. package/dist/integrations/linear/webhook-server.js +7 -7
  35. package/dist/integrations/linear/webhook-server.js.map +2 -2
  36. package/dist/integrations/linear/webhook.js +9 -2
  37. package/dist/integrations/linear/webhook.js.map +2 -2
  38. package/dist/integrations/mcp/schemas.js +147 -0
  39. package/dist/integrations/mcp/schemas.js.map +7 -0
  40. package/dist/integrations/mcp/server.js +19 -3
  41. package/dist/integrations/mcp/server.js.map +2 -2
  42. package/package.json +1 -1
@@ -4,6 +4,7 @@ const __filename = __fileURLToPath(import.meta.url);
4
4
  const __dirname = __pathDirname(__filename);
5
5
  import { LinearRestClient } from "./rest-client.js";
6
6
  import { logger } from "../../core/monitoring/logger.js";
7
+ import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
7
8
  import chalk from "chalk";
8
9
  class LinearMigrator {
9
10
  sourceClient;
@@ -71,26 +72,37 @@ class LinearMigrator {
71
72
  taskMappings: []
72
73
  };
73
74
  try {
74
- console.log(chalk.yellow("\u{1F504} Starting Linear workspace migration..."));
75
+ console.log(chalk.yellow("Starting Linear workspace migration..."));
75
76
  const sourceTasks = await this.sourceClient.getAllTasks(true);
76
77
  result.totalTasks = sourceTasks.length;
77
- console.log(chalk.cyan(`\u{1F4CB} Found ${sourceTasks.length} tasks in source workspace`));
78
+ console.log(
79
+ chalk.cyan(`Found ${sourceTasks.length} tasks in source workspace`)
80
+ );
78
81
  let tasksToMigrate = sourceTasks;
79
82
  if (this.config.taskPrefix) {
80
83
  tasksToMigrate = sourceTasks.filter(
81
84
  (task) => task.identifier.startsWith(this.config.taskPrefix)
82
85
  );
83
- console.log(chalk.cyan(`\u{1F4CB} Filtered to ${tasksToMigrate.length} tasks with prefix "${this.config.taskPrefix}"`));
86
+ console.log(
87
+ chalk.cyan(
88
+ `Filtered to ${tasksToMigrate.length} tasks with prefix "${this.config.taskPrefix}"`
89
+ )
90
+ );
84
91
  }
85
92
  if (this.config.includeStates?.length) {
86
93
  tasksToMigrate = tasksToMigrate.filter(
87
94
  (task) => this.config.includeStates.includes(task.state.type)
88
95
  );
89
- console.log(chalk.cyan(`\u{1F4CB} Further filtered to ${tasksToMigrate.length} tasks matching states: ${this.config.includeStates.join(", ")}`));
96
+ const stateStr = this.config.includeStates.join(", ");
97
+ console.log(
98
+ chalk.cyan(
99
+ `Further filtered to ${tasksToMigrate.length} tasks matching states: ${stateStr}`
100
+ )
101
+ );
90
102
  }
91
103
  result.exported = tasksToMigrate.length;
92
104
  if (this.config.dryRun) {
93
- console.log(chalk.yellow("\u{1F50D} DRY RUN - No tasks will be created"));
105
+ console.log(chalk.yellow("DRY RUN - No tasks will be created"));
94
106
  tasksToMigrate.forEach((task) => {
95
107
  result.taskMappings.push({
96
108
  sourceId: task.id,
@@ -103,12 +115,18 @@ class LinearMigrator {
103
115
  return result;
104
116
  }
105
117
  const targetTeam = await this.targetClient.getTeam();
106
- console.log(chalk.cyan(`\u{1F3AF} Target team: ${targetTeam.name} (${targetTeam.key})`));
118
+ console.log(
119
+ chalk.cyan(`Target team: ${targetTeam.name} (${targetTeam.key})`)
120
+ );
107
121
  const batchSize = this.config.batchSize || 5;
108
122
  const delayMs = this.config.delayMs || 2e3;
109
123
  for (let i = 0; i < tasksToMigrate.length; i += batchSize) {
110
124
  const batch = tasksToMigrate.slice(i, i + batchSize);
111
- console.log(chalk.yellow(`\u{1F4E6} Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(tasksToMigrate.length / batchSize)}`));
125
+ console.log(
126
+ chalk.yellow(
127
+ `Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(tasksToMigrate.length / batchSize)}`
128
+ )
129
+ );
112
130
  for (const task of batch) {
113
131
  try {
114
132
  const newTask = await this.migrateTask(task, targetTeam.id);
@@ -120,17 +138,29 @@ class LinearMigrator {
120
138
  deleted: false
121
139
  };
122
140
  result.imported++;
123
- console.log(chalk.green(`\u2705 ${task.identifier} \u2192 ${newTask.identifier}: ${task.title}`));
141
+ console.log(
142
+ chalk.green(
143
+ `${task.identifier} \u2192 ${newTask.identifier}: ${task.title}`
144
+ )
145
+ );
124
146
  if (this.config.deleteFromSource) {
125
147
  try {
126
148
  await this.deleteTask(task.id);
127
149
  mapping.deleted = true;
128
150
  result.deleted++;
129
- console.log(chalk.gray(`\u{1F5D1}\uFE0F Deleted ${task.identifier} from source`));
151
+ console.log(
152
+ chalk.gray(`Deleted ${task.identifier} from source`)
153
+ );
130
154
  } catch (deleteError) {
131
155
  result.deleteFailed++;
132
- result.errors.push(`Delete failed for ${task.identifier}: ${deleteError.message}`);
133
- console.log(chalk.yellow(`\u26A0\uFE0F Failed to delete ${task.identifier} from source: ${deleteError.message}`));
156
+ result.errors.push(
157
+ `Delete failed for ${task.identifier}: ${deleteError.message}`
158
+ );
159
+ console.log(
160
+ chalk.yellow(
161
+ `Failed to delete ${task.identifier} from source: ${deleteError.message}`
162
+ )
163
+ );
134
164
  }
135
165
  }
136
166
  result.taskMappings.push(mapping);
@@ -143,11 +173,11 @@ class LinearMigrator {
143
173
  error: errorMsg
144
174
  });
145
175
  result.failed++;
146
- console.log(chalk.red(`\u274C ${task.identifier}: ${errorMsg}`));
176
+ console.log(chalk.red(`${task.identifier}: ${errorMsg}`));
147
177
  }
148
178
  }
149
179
  if (i + batchSize < tasksToMigrate.length) {
150
- console.log(chalk.gray(`\u23F3 Waiting ${delayMs}ms before next batch...`));
180
+ console.log(chalk.gray(`Waiting ${delayMs}ms before next batch...`));
151
181
  await this.delay(delayMs);
152
182
  }
153
183
  }
@@ -162,11 +192,11 @@ class LinearMigrator {
162
192
  */
163
193
  async migrateTask(sourceTask, targetTeamId) {
164
194
  const stateMapping = {
165
- "backlog": "backlog",
166
- "unstarted": "unstarted",
167
- "started": "started",
168
- "completed": "completed",
169
- "canceled": "canceled"
195
+ backlog: "backlog",
196
+ unstarted: "unstarted",
197
+ started: "started",
198
+ completed: "completed",
199
+ canceled: "canceled"
170
200
  };
171
201
  const createTaskQuery = `
172
202
  mutation CreateIssue($input: IssueCreateInput!) {
@@ -198,7 +228,11 @@ class LinearMigrator {
198
228
  };
199
229
  const response = await this.targetClient.makeRequest(createTaskQuery, { input: taskInput });
200
230
  if (!response.data?.issueCreate?.success) {
201
- throw new Error("Failed to create task in target workspace");
231
+ throw new IntegrationError(
232
+ "Failed to create task in target workspace",
233
+ ErrorCode.LINEAR_SYNC_FAILED,
234
+ { sourceTaskId: sourceTask.id, sourceIdentifier: sourceTask.identifier }
235
+ );
202
236
  }
203
237
  return response.data.issueCreate.issue;
204
238
  }
@@ -245,9 +279,15 @@ class LinearMigrator {
245
279
  }
246
280
  }
247
281
  `;
248
- const response = await this.sourceClient.makeRequest(deleteQuery, { id: taskId });
282
+ const response = await this.sourceClient.makeRequest(deleteQuery, {
283
+ id: taskId
284
+ });
249
285
  if (!response.data?.issueDelete?.success) {
250
- throw new Error("Failed to delete task from source workspace");
286
+ throw new IntegrationError(
287
+ "Failed to delete task from source workspace",
288
+ ErrorCode.LINEAR_SYNC_FAILED,
289
+ { taskId }
290
+ );
251
291
  }
252
292
  }
253
293
  /**
@@ -259,40 +299,58 @@ class LinearMigrator {
259
299
  }
260
300
  async function runMigration(config) {
261
301
  const migrator = new LinearMigrator(config);
262
- console.log(chalk.blue("\u{1F50D} Testing connections..."));
302
+ console.log(chalk.blue("Testing connections..."));
263
303
  const connectionTest = await migrator.testConnections();
264
304
  if (!connectionTest.source.success) {
265
- console.error(chalk.red(`\u274C Source connection failed: ${connectionTest.source.error}`));
305
+ console.error(
306
+ chalk.red(`Source connection failed: ${connectionTest.source.error}`)
307
+ );
266
308
  return;
267
309
  }
268
310
  if (!connectionTest.target.success) {
269
- console.error(chalk.red(`\u274C Target connection failed: ${connectionTest.target.error}`));
311
+ console.error(
312
+ chalk.red(`Target connection failed: ${connectionTest.target.error}`)
313
+ );
270
314
  return;
271
315
  }
272
- console.log(chalk.green("\u2705 Both connections successful"));
273
- console.log(chalk.cyan(`\u{1F4E4} Source: ${connectionTest.source.info.user.name} @ ${connectionTest.source.info.team.name}`));
274
- console.log(chalk.cyan(`\u{1F4E5} Target: ${connectionTest.target.info.user.name} @ ${connectionTest.target.info.team.name}`));
316
+ console.log(chalk.green("Both connections successful"));
317
+ console.log(
318
+ chalk.cyan(
319
+ `Source: ${connectionTest.source.info.user.name} @ ${connectionTest.source.info.team.name}`
320
+ )
321
+ );
322
+ console.log(
323
+ chalk.cyan(
324
+ `Target: ${connectionTest.target.info.user.name} @ ${connectionTest.target.info.team.name}`
325
+ )
326
+ );
275
327
  const result = await migrator.migrate();
276
- console.log(chalk.blue("\n\u{1F4CA} Migration Summary:"));
328
+ console.log(chalk.blue("\nMigration Summary:"));
277
329
  console.log(` Total tasks: ${result.totalTasks}`);
278
330
  console.log(` Exported: ${result.exported}`);
279
- console.log(chalk.green(` \u2705 Imported: ${result.imported}`));
280
- console.log(chalk.red(` \u274C Failed: ${result.failed}`));
331
+ console.log(chalk.green(` Imported: ${result.imported}`));
332
+ console.log(chalk.red(` Failed: ${result.failed}`));
281
333
  if (config.deleteFromSource) {
282
- console.log(chalk.gray(` \u{1F5D1}\uFE0F Deleted: ${result.deleted}`));
334
+ console.log(chalk.gray(` Deleted: ${result.deleted}`));
283
335
  if (result.deleteFailed > 0) {
284
- console.log(chalk.yellow(` \u26A0\uFE0F Delete failed: ${result.deleteFailed}`));
336
+ console.log(chalk.yellow(` Delete failed: ${result.deleteFailed}`));
285
337
  }
286
338
  }
287
339
  if (result.errors.length > 0) {
288
- console.log(chalk.red("\n\u274C Errors:"));
340
+ console.log(chalk.red("\nErrors:"));
289
341
  result.errors.forEach((error) => console.log(chalk.red(` - ${error}`)));
290
342
  }
291
343
  if (result.imported > 0) {
292
- console.log(chalk.green(`
293
- \u{1F389} Migration completed! ${result.imported} tasks migrated successfully.`));
344
+ console.log(
345
+ chalk.green(
346
+ `
347
+ Migration completed! ${result.imported} tasks migrated successfully.`
348
+ )
349
+ );
294
350
  if (config.deleteFromSource && result.deleted > 0) {
295
- console.log(chalk.gray(` ${result.deleted} tasks deleted from source workspace.`));
351
+ console.log(
352
+ chalk.gray(` ${result.deleted} tasks deleted from source workspace.`)
353
+ );
296
354
  }
297
355
  }
298
356
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/integrations/linear/migration.ts"],
4
- "sourcesContent": ["/**\n * Linear Workspace Migration Tool\n * Migrates all tasks from one Linear workspace to another\n */\n\nimport { LinearRestClient } from './rest-client.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport chalk from 'chalk';\n\nexport interface MigrationConfig {\n sourceApiKey: string;\n targetApiKey: string;\n dryRun?: boolean;\n includeStates?: string[]; // Filter by state\n taskPrefix?: string; // Only migrate tasks with this identifier prefix (e.g., \"STA-\")\n deleteFromSource?: boolean; // Delete tasks from source after successful migration\n batchSize?: number;\n delayMs?: number; // Delay between API calls\n}\n\nexport interface MigrationResult {\n totalTasks: number;\n exported: number;\n imported: number;\n failed: number;\n deleted: number;\n deleteFailed: number;\n errors: string[];\n taskMappings: Array<{\n sourceId: string;\n sourceIdentifier: string;\n targetId?: string;\n targetIdentifier?: string;\n deleted?: boolean;\n error?: string;\n }>;\n}\n\nexport class LinearMigrator {\n private sourceClient: LinearRestClient;\n private targetClient: LinearRestClient;\n private config: MigrationConfig;\n\n constructor(config: MigrationConfig) {\n this.config = config;\n this.sourceClient = new LinearRestClient(config.sourceApiKey);\n this.targetClient = new LinearRestClient(config.targetApiKey);\n }\n\n /**\n * Test connections to both workspaces\n */\n async testConnections(): Promise<{\n source: { success: boolean; info?: any; error?: string };\n target: { success: boolean; info?: any; error?: string };\n }> {\n const result = {\n source: { success: false } as any,\n target: { success: false } as any\n };\n\n // Test source connection\n try {\n const sourceViewer = await this.sourceClient.getViewer();\n const sourceTeam = await this.sourceClient.getTeam();\n result.source = {\n success: true,\n info: {\n user: sourceViewer,\n team: sourceTeam\n }\n };\n } catch (error: unknown) {\n result.source = {\n success: false,\n error: (error as Error).message\n };\n }\n\n // Test target connection \n try {\n const targetViewer = await this.targetClient.getViewer();\n const targetTeam = await this.targetClient.getTeam();\n result.target = {\n success: true,\n info: {\n user: targetViewer,\n team: targetTeam\n }\n };\n } catch (error: unknown) {\n result.target = {\n success: false,\n error: (error as Error).message\n };\n }\n\n return result;\n }\n\n /**\n * Migrate all tasks from source to target workspace\n */\n async migrate(): Promise<MigrationResult> {\n const result: MigrationResult = {\n totalTasks: 0,\n exported: 0,\n imported: 0,\n failed: 0,\n deleted: 0,\n deleteFailed: 0,\n errors: [],\n taskMappings: []\n };\n\n try {\n console.log(chalk.yellow('\uD83D\uDD04 Starting Linear workspace migration...'));\n\n // Get all tasks from source\n const sourceTasks = await this.sourceClient.getAllTasks(true); // Force refresh\n result.totalTasks = sourceTasks.length;\n console.log(chalk.cyan(`\uD83D\uDCCB Found ${sourceTasks.length} tasks in source workspace`));\n\n // Filter by prefix (e.g., \"STA-\" tasks only)\n let tasksToMigrate = sourceTasks;\n if (this.config.taskPrefix) {\n tasksToMigrate = sourceTasks.filter((task: any) => \n task.identifier.startsWith(this.config.taskPrefix!)\n );\n console.log(chalk.cyan(`\uD83D\uDCCB Filtered to ${tasksToMigrate.length} tasks with prefix \"${this.config.taskPrefix}\"`));\n }\n\n // Filter by states if specified\n if (this.config.includeStates?.length) {\n tasksToMigrate = tasksToMigrate.filter((task: any) => \n this.config.includeStates!.includes(task.state.type)\n );\n console.log(chalk.cyan(`\uD83D\uDCCB Further filtered to ${tasksToMigrate.length} tasks matching states: ${this.config.includeStates.join(', ')}`));\n }\n\n result.exported = tasksToMigrate.length;\n\n if (this.config.dryRun) {\n console.log(chalk.yellow('\uD83D\uDD0D DRY RUN - No tasks will be created'));\n tasksToMigrate.forEach(task => {\n result.taskMappings.push({\n sourceId: task.id,\n sourceIdentifier: task.identifier,\n targetId: 'DRY_RUN',\n targetIdentifier: 'DRY_RUN'\n });\n });\n result.imported = tasksToMigrate.length;\n return result;\n }\n\n // Get target team info\n const targetTeam = await this.targetClient.getTeam();\n console.log(chalk.cyan(`\uD83C\uDFAF Target team: ${targetTeam.name} (${targetTeam.key})`));\n\n // Migrate tasks in batches\n const batchSize = this.config.batchSize || 5;\n const delayMs = this.config.delayMs || 2000;\n\n for (let i = 0; i < tasksToMigrate.length; i += batchSize) {\n const batch = tasksToMigrate.slice(i, i + batchSize);\n console.log(chalk.yellow(`\uD83D\uDCE6 Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(tasksToMigrate.length / batchSize)}`));\n\n for (const task of batch) {\n try {\n const newTask = await this.migrateTask(task, targetTeam.id);\n const mapping = {\n sourceId: task.id,\n sourceIdentifier: task.identifier,\n targetId: newTask.id,\n targetIdentifier: newTask.identifier,\n deleted: false\n };\n\n result.imported++;\n console.log(chalk.green(`\u2705 ${task.identifier} \u2192 ${newTask.identifier}: ${task.title}`));\n\n // Delete from source if configured\n if (this.config.deleteFromSource) {\n try {\n await this.deleteTask(task.id);\n mapping.deleted = true;\n result.deleted++;\n console.log(chalk.gray(`\uD83D\uDDD1\uFE0F Deleted ${task.identifier} from source`));\n } catch (deleteError: unknown) {\n result.deleteFailed++;\n result.errors.push(`Delete failed for ${task.identifier}: ${(deleteError as Error).message}`);\n console.log(chalk.yellow(`\u26A0\uFE0F Failed to delete ${task.identifier} from source: ${(deleteError as Error).message}`));\n }\n }\n\n result.taskMappings.push(mapping);\n } catch (error: unknown) {\n const errorMsg = (error as Error).message;\n result.errors.push(`${task.identifier}: ${errorMsg}`);\n result.taskMappings.push({\n sourceId: task.id,\n sourceIdentifier: task.identifier,\n error: errorMsg\n });\n result.failed++;\n console.log(chalk.red(`\u274C ${task.identifier}: ${errorMsg}`));\n }\n }\n\n // Delay between batches to avoid rate limits\n if (i + batchSize < tasksToMigrate.length) {\n console.log(chalk.gray(`\u23F3 Waiting ${delayMs}ms before next batch...`));\n await this.delay(delayMs);\n }\n }\n\n } catch (error: unknown) {\n result.errors.push(`Migration failed: ${(error as Error).message}`);\n logger.error('Migration failed:', error as Error);\n }\n\n return result;\n }\n\n /**\n * Migrate a single task\n */\n private async migrateTask(sourceTask: any, targetTeamId: string): Promise<any> {\n // Map states from source to target format\n const stateMapping: Record<string, string> = {\n 'backlog': 'backlog',\n 'unstarted': 'unstarted', \n 'started': 'started',\n 'completed': 'completed',\n 'canceled': 'canceled'\n };\n\n // Create task in target workspace using GraphQL\n const createTaskQuery = `\n mutation CreateIssue($input: IssueCreateInput!) {\n issueCreate(input: $input) {\n success\n issue {\n id\n identifier\n title\n description\n state {\n id\n name\n type\n }\n priority\n createdAt\n updatedAt\n url\n }\n }\n }\n `;\n\n // Prepare task input\n const taskInput = {\n title: `[MIGRATED] ${sourceTask.title}`,\n description: this.formatMigratedDescription(sourceTask),\n teamId: targetTeamId,\n priority: this.mapPriority(sourceTask.priority)\n };\n\n const response = await this.targetClient.makeRequest<{\n data: {\n issueCreate: {\n success: boolean;\n issue: any;\n };\n };\n }>(createTaskQuery, { input: taskInput });\n\n if (!response.data?.issueCreate?.success) {\n throw new Error('Failed to create task in target workspace');\n }\n\n return response.data.issueCreate.issue;\n }\n\n /**\n * Format description with migration context\n */\n private formatMigratedDescription(sourceTask: any): string {\n let description = sourceTask.description || '';\n \n description += `\\n\\n---\\n**Migration Info:**\\n`;\n description += `- Original ID: ${sourceTask.identifier}\\n`;\n description += `- Migrated: ${new Date().toISOString()}\\n`;\n description += `- Original State: ${sourceTask.state.name}\\n`;\n \n if (sourceTask.assignee) {\n description += `- Original Assignee: ${sourceTask.assignee.name}\\n`;\n }\n \n if (sourceTask.estimate) {\n description += `- Original Estimate: ${sourceTask.estimate} points\\n`;\n }\n\n return description;\n }\n\n /**\n * Map priority values\n */\n private mapPriority(priority?: number): number {\n // Linear priorities: 0=none, 1=urgent, 2=high, 3=medium, 4=low\n return priority || 0;\n }\n\n /**\n * Delete a task from the source workspace\n */\n private async deleteTask(taskId: string): Promise<void> {\n const deleteQuery = `\n mutation DeleteIssue($id: String!) {\n issueDelete(id: $id) {\n success\n }\n }\n `;\n\n const response = await (this.sourceClient as any).makeRequest(deleteQuery, { id: taskId });\n \n if (!response.data?.issueDelete?.success) {\n throw new Error('Failed to delete task from source workspace');\n }\n }\n\n /**\n * Delay helper\n */\n private delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n\n/**\n * CLI function to run migration\n */\nexport async function runMigration(config: MigrationConfig): Promise<void> {\n const migrator = new LinearMigrator(config);\n \n console.log(chalk.blue('\uD83D\uDD0D Testing connections...'));\n const connectionTest = await migrator.testConnections();\n \n if (!connectionTest.source.success) {\n console.error(chalk.red(`\u274C Source connection failed: ${connectionTest.source.error}`));\n return;\n }\n \n if (!connectionTest.target.success) {\n console.error(chalk.red(`\u274C Target connection failed: ${connectionTest.target.error}`));\n return;\n }\n \n console.log(chalk.green('\u2705 Both connections successful'));\n console.log(chalk.cyan(`\uD83D\uDCE4 Source: ${connectionTest.source.info.user.name} @ ${connectionTest.source.info.team.name}`));\n console.log(chalk.cyan(`\uD83D\uDCE5 Target: ${connectionTest.target.info.user.name} @ ${connectionTest.target.info.team.name}`));\n \n const result = await migrator.migrate();\n \n console.log(chalk.blue('\\n\uD83D\uDCCA Migration Summary:'));\n console.log(` Total tasks: ${result.totalTasks}`);\n console.log(` Exported: ${result.exported}`);\n console.log(chalk.green(` \u2705 Imported: ${result.imported}`));\n console.log(chalk.red(` \u274C Failed: ${result.failed}`));\n if (config.deleteFromSource) {\n console.log(chalk.gray(` \uD83D\uDDD1\uFE0F Deleted: ${result.deleted}`));\n if (result.deleteFailed > 0) {\n console.log(chalk.yellow(` \u26A0\uFE0F Delete failed: ${result.deleteFailed}`));\n }\n }\n \n if (result.errors.length > 0) {\n console.log(chalk.red('\\n\u274C Errors:'));\n result.errors.forEach(error => console.log(chalk.red(` - ${error}`)));\n }\n \n if (result.imported > 0) {\n console.log(chalk.green(`\\n\uD83C\uDF89 Migration completed! ${result.imported} tasks migrated successfully.`));\n if (config.deleteFromSource && result.deleted > 0) {\n console.log(chalk.gray(` ${result.deleted} tasks deleted from source workspace.`));\n }\n }\n}"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Linear Workspace Migration Tool\n * Migrates all tasks from one Linear workspace to another\n */\n\nimport { LinearRestClient } from './rest-client.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport { IntegrationError, ErrorCode } from '../../core/errors/index.js';\nimport chalk from 'chalk';\n\nexport interface MigrationConfig {\n sourceApiKey: string;\n targetApiKey: string;\n dryRun?: boolean;\n includeStates?: string[]; // Filter by state\n taskPrefix?: string; // Only migrate tasks with this identifier prefix (e.g., \"STA-\")\n deleteFromSource?: boolean; // Delete tasks from source after successful migration\n batchSize?: number;\n delayMs?: number; // Delay between API calls\n}\n\nexport interface MigrationResult {\n totalTasks: number;\n exported: number;\n imported: number;\n failed: number;\n deleted: number;\n deleteFailed: number;\n errors: string[];\n taskMappings: Array<{\n sourceId: string;\n sourceIdentifier: string;\n targetId?: string;\n targetIdentifier?: string;\n deleted?: boolean;\n error?: string;\n }>;\n}\n\nexport class LinearMigrator {\n private sourceClient: LinearRestClient;\n private targetClient: LinearRestClient;\n private config: MigrationConfig;\n\n constructor(config: MigrationConfig) {\n this.config = config;\n this.sourceClient = new LinearRestClient(config.sourceApiKey);\n this.targetClient = new LinearRestClient(config.targetApiKey);\n }\n\n /**\n * Test connections to both workspaces\n */\n async testConnections(): Promise<{\n source: { success: boolean; info?: any; error?: string };\n target: { success: boolean; info?: any; error?: string };\n }> {\n const result = {\n source: { success: false } as any,\n target: { success: false } as any,\n };\n\n // Test source connection\n try {\n const sourceViewer = await this.sourceClient.getViewer();\n const sourceTeam = await this.sourceClient.getTeam();\n result.source = {\n success: true,\n info: {\n user: sourceViewer,\n team: sourceTeam,\n },\n };\n } catch (error: unknown) {\n result.source = {\n success: false,\n error: (error as Error).message,\n };\n }\n\n // Test target connection\n try {\n const targetViewer = await this.targetClient.getViewer();\n const targetTeam = await this.targetClient.getTeam();\n result.target = {\n success: true,\n info: {\n user: targetViewer,\n team: targetTeam,\n },\n };\n } catch (error: unknown) {\n result.target = {\n success: false,\n error: (error as Error).message,\n };\n }\n\n return result;\n }\n\n /**\n * Migrate all tasks from source to target workspace\n */\n async migrate(): Promise<MigrationResult> {\n const result: MigrationResult = {\n totalTasks: 0,\n exported: 0,\n imported: 0,\n failed: 0,\n deleted: 0,\n deleteFailed: 0,\n errors: [],\n taskMappings: [],\n };\n\n try {\n console.log(chalk.yellow('Starting Linear workspace migration...'));\n\n // Get all tasks from source\n const sourceTasks = await this.sourceClient.getAllTasks(true); // Force refresh\n result.totalTasks = sourceTasks.length;\n console.log(\n chalk.cyan(`Found ${sourceTasks.length} tasks in source workspace`)\n );\n\n // Filter by prefix (e.g., \"STA-\" tasks only)\n let tasksToMigrate = sourceTasks;\n if (this.config.taskPrefix) {\n tasksToMigrate = sourceTasks.filter((task: any) =>\n task.identifier.startsWith(this.config.taskPrefix!)\n );\n console.log(\n chalk.cyan(\n `Filtered to ${tasksToMigrate.length} tasks with prefix \"${this.config.taskPrefix}\"`\n )\n );\n }\n\n // Filter by states if specified\n if (this.config.includeStates?.length) {\n tasksToMigrate = tasksToMigrate.filter((task: any) =>\n this.config.includeStates!.includes(task.state.type)\n );\n const stateStr = this.config.includeStates.join(', ');\n console.log(\n chalk.cyan(\n `Further filtered to ${tasksToMigrate.length} tasks matching states: ${stateStr}`\n )\n );\n }\n\n result.exported = tasksToMigrate.length;\n\n if (this.config.dryRun) {\n console.log(chalk.yellow('DRY RUN - No tasks will be created'));\n tasksToMigrate.forEach((task) => {\n result.taskMappings.push({\n sourceId: task.id,\n sourceIdentifier: task.identifier,\n targetId: 'DRY_RUN',\n targetIdentifier: 'DRY_RUN',\n });\n });\n result.imported = tasksToMigrate.length;\n return result;\n }\n\n // Get target team info\n const targetTeam = await this.targetClient.getTeam();\n console.log(\n chalk.cyan(`Target team: ${targetTeam.name} (${targetTeam.key})`)\n );\n\n // Migrate tasks in batches\n const batchSize = this.config.batchSize || 5;\n const delayMs = this.config.delayMs || 2000;\n\n for (let i = 0; i < tasksToMigrate.length; i += batchSize) {\n const batch = tasksToMigrate.slice(i, i + batchSize);\n console.log(\n chalk.yellow(\n `Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(tasksToMigrate.length / batchSize)}`\n )\n );\n\n for (const task of batch) {\n try {\n const newTask = await this.migrateTask(task, targetTeam.id);\n const mapping = {\n sourceId: task.id,\n sourceIdentifier: task.identifier,\n targetId: newTask.id,\n targetIdentifier: newTask.identifier,\n deleted: false,\n };\n\n result.imported++;\n console.log(\n chalk.green(\n `${task.identifier} \u2192 ${newTask.identifier}: ${task.title}`\n )\n );\n\n // Delete from source if configured\n if (this.config.deleteFromSource) {\n try {\n await this.deleteTask(task.id);\n mapping.deleted = true;\n result.deleted++;\n console.log(\n chalk.gray(`Deleted ${task.identifier} from source`)\n );\n } catch (deleteError: unknown) {\n result.deleteFailed++;\n result.errors.push(\n `Delete failed for ${task.identifier}: ${(deleteError as Error).message}`\n );\n console.log(\n chalk.yellow(\n `Failed to delete ${task.identifier} from source: ${(deleteError as Error).message}`\n )\n );\n }\n }\n\n result.taskMappings.push(mapping);\n } catch (error: unknown) {\n const errorMsg = (error as Error).message;\n result.errors.push(`${task.identifier}: ${errorMsg}`);\n result.taskMappings.push({\n sourceId: task.id,\n sourceIdentifier: task.identifier,\n error: errorMsg,\n });\n result.failed++;\n console.log(chalk.red(`${task.identifier}: ${errorMsg}`));\n }\n }\n\n // Delay between batches to avoid rate limits\n if (i + batchSize < tasksToMigrate.length) {\n console.log(chalk.gray(`Waiting ${delayMs}ms before next batch...`));\n await this.delay(delayMs);\n }\n }\n } catch (error: unknown) {\n result.errors.push(`Migration failed: ${(error as Error).message}`);\n logger.error('Migration failed:', error as Error);\n }\n\n return result;\n }\n\n /**\n * Migrate a single task\n */\n private async migrateTask(\n sourceTask: any,\n targetTeamId: string\n ): Promise<any> {\n // Map states from source to target format\n const stateMapping: Record<string, string> = {\n backlog: 'backlog',\n unstarted: 'unstarted',\n started: 'started',\n completed: 'completed',\n canceled: 'canceled',\n };\n\n // Create task in target workspace using GraphQL\n const createTaskQuery = `\n mutation CreateIssue($input: IssueCreateInput!) {\n issueCreate(input: $input) {\n success\n issue {\n id\n identifier\n title\n description\n state {\n id\n name\n type\n }\n priority\n createdAt\n updatedAt\n url\n }\n }\n }\n `;\n\n // Prepare task input\n const taskInput = {\n title: `[MIGRATED] ${sourceTask.title}`,\n description: this.formatMigratedDescription(sourceTask),\n teamId: targetTeamId,\n priority: this.mapPriority(sourceTask.priority),\n };\n\n const response = await this.targetClient.makeRequest<{\n data: {\n issueCreate: {\n success: boolean;\n issue: any;\n };\n };\n }>(createTaskQuery, { input: taskInput });\n\n if (!response.data?.issueCreate?.success) {\n throw new IntegrationError(\n 'Failed to create task in target workspace',\n ErrorCode.LINEAR_SYNC_FAILED,\n { sourceTaskId: sourceTask.id, sourceIdentifier: sourceTask.identifier }\n );\n }\n\n return response.data.issueCreate.issue;\n }\n\n /**\n * Format description with migration context\n */\n private formatMigratedDescription(sourceTask: any): string {\n let description = sourceTask.description || '';\n\n description += `\\n\\n---\\n**Migration Info:**\\n`;\n description += `- Original ID: ${sourceTask.identifier}\\n`;\n description += `- Migrated: ${new Date().toISOString()}\\n`;\n description += `- Original State: ${sourceTask.state.name}\\n`;\n\n if (sourceTask.assignee) {\n description += `- Original Assignee: ${sourceTask.assignee.name}\\n`;\n }\n\n if (sourceTask.estimate) {\n description += `- Original Estimate: ${sourceTask.estimate} points\\n`;\n }\n\n return description;\n }\n\n /**\n * Map priority values\n */\n private mapPriority(priority?: number): number {\n // Linear priorities: 0=none, 1=urgent, 2=high, 3=medium, 4=low\n return priority || 0;\n }\n\n /**\n * Delete a task from the source workspace\n */\n private async deleteTask(taskId: string): Promise<void> {\n const deleteQuery = `\n mutation DeleteIssue($id: String!) {\n issueDelete(id: $id) {\n success\n }\n }\n `;\n\n const response = await (this.sourceClient as any).makeRequest(deleteQuery, {\n id: taskId,\n });\n\n if (!response.data?.issueDelete?.success) {\n throw new IntegrationError(\n 'Failed to delete task from source workspace',\n ErrorCode.LINEAR_SYNC_FAILED,\n { taskId }\n );\n }\n }\n\n /**\n * Delay helper\n */\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\n/**\n * CLI function to run migration\n */\nexport async function runMigration(config: MigrationConfig): Promise<void> {\n const migrator = new LinearMigrator(config);\n\n console.log(chalk.blue('Testing connections...'));\n const connectionTest = await migrator.testConnections();\n\n if (!connectionTest.source.success) {\n console.error(\n chalk.red(`Source connection failed: ${connectionTest.source.error}`)\n );\n return;\n }\n\n if (!connectionTest.target.success) {\n console.error(\n chalk.red(`Target connection failed: ${connectionTest.target.error}`)\n );\n return;\n }\n\n console.log(chalk.green('Both connections successful'));\n console.log(\n chalk.cyan(\n `Source: ${connectionTest.source.info.user.name} @ ${connectionTest.source.info.team.name}`\n )\n );\n console.log(\n chalk.cyan(\n `Target: ${connectionTest.target.info.user.name} @ ${connectionTest.target.info.team.name}`\n )\n );\n\n const result = await migrator.migrate();\n\n console.log(chalk.blue('\\nMigration Summary:'));\n console.log(` Total tasks: ${result.totalTasks}`);\n console.log(` Exported: ${result.exported}`);\n console.log(chalk.green(` Imported: ${result.imported}`));\n console.log(chalk.red(` Failed: ${result.failed}`));\n if (config.deleteFromSource) {\n console.log(chalk.gray(` Deleted: ${result.deleted}`));\n if (result.deleteFailed > 0) {\n console.log(chalk.yellow(` Delete failed: ${result.deleteFailed}`));\n }\n }\n\n if (result.errors.length > 0) {\n console.log(chalk.red('\\nErrors:'));\n result.errors.forEach((error) => console.log(chalk.red(` - ${error}`)));\n }\n\n if (result.imported > 0) {\n console.log(\n chalk.green(\n `\\nMigration completed! ${result.imported} tasks migrated successfully.`\n )\n );\n if (config.deleteFromSource && result.deleted > 0) {\n console.log(\n chalk.gray(` ${result.deleted} tasks deleted from source workspace.`)\n );\n }\n }\n}\n"],
5
+ "mappings": ";;;;AAKA,SAAS,wBAAwB;AACjC,SAAS,cAAc;AACvB,SAAS,kBAAkB,iBAAiB;AAC5C,OAAO,WAAW;AA+BX,MAAM,eAAe;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,QAAyB;AACnC,SAAK,SAAS;AACd,SAAK,eAAe,IAAI,iBAAiB,OAAO,YAAY;AAC5D,SAAK,eAAe,IAAI,iBAAiB,OAAO,YAAY;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,kBAGH;AACD,UAAM,SAAS;AAAA,MACb,QAAQ,EAAE,SAAS,MAAM;AAAA,MACzB,QAAQ,EAAE,SAAS,MAAM;AAAA,IAC3B;AAGA,QAAI;AACF,YAAM,eAAe,MAAM,KAAK,aAAa,UAAU;AACvD,YAAM,aAAa,MAAM,KAAK,aAAa,QAAQ;AACnD,aAAO,SAAS;AAAA,QACd,SAAS;AAAA,QACT,MAAM;AAAA,UACJ,MAAM;AAAA,UACN,MAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF,SAAS,OAAgB;AACvB,aAAO,SAAS;AAAA,QACd,SAAS;AAAA,QACT,OAAQ,MAAgB;AAAA,MAC1B;AAAA,IACF;AAGA,QAAI;AACF,YAAM,eAAe,MAAM,KAAK,aAAa,UAAU;AACvD,YAAM,aAAa,MAAM,KAAK,aAAa,QAAQ;AACnD,aAAO,SAAS;AAAA,QACd,SAAS;AAAA,QACT,MAAM;AAAA,UACJ,MAAM;AAAA,UACN,MAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF,SAAS,OAAgB;AACvB,aAAO,SAAS;AAAA,QACd,SAAS;AAAA,QACT,OAAQ,MAAgB;AAAA,MAC1B;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAoC;AACxC,UAAM,SAA0B;AAAA,MAC9B,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,cAAc;AAAA,MACd,QAAQ,CAAC;AAAA,MACT,cAAc,CAAC;AAAA,IACjB;AAEA,QAAI;AACF,cAAQ,IAAI,MAAM,OAAO,wCAAwC,CAAC;AAGlE,YAAM,cAAc,MAAM,KAAK,aAAa,YAAY,IAAI;AAC5D,aAAO,aAAa,YAAY;AAChC,cAAQ;AAAA,QACN,MAAM,KAAK,SAAS,YAAY,MAAM,4BAA4B;AAAA,MACpE;AAGA,UAAI,iBAAiB;AACrB,UAAI,KAAK,OAAO,YAAY;AAC1B,yBAAiB,YAAY;AAAA,UAAO,CAAC,SACnC,KAAK,WAAW,WAAW,KAAK,OAAO,UAAW;AAAA,QACpD;AACA,gBAAQ;AAAA,UACN,MAAM;AAAA,YACJ,eAAe,eAAe,MAAM,uBAAuB,KAAK,OAAO,UAAU;AAAA,UACnF;AAAA,QACF;AAAA,MACF;AAGA,UAAI,KAAK,OAAO,eAAe,QAAQ;AACrC,yBAAiB,eAAe;AAAA,UAAO,CAAC,SACtC,KAAK,OAAO,cAAe,SAAS,KAAK,MAAM,IAAI;AAAA,QACrD;AACA,cAAM,WAAW,KAAK,OAAO,cAAc,KAAK,IAAI;AACpD,gBAAQ;AAAA,UACN,MAAM;AAAA,YACJ,uBAAuB,eAAe,MAAM,2BAA2B,QAAQ;AAAA,UACjF;AAAA,QACF;AAAA,MACF;AAEA,aAAO,WAAW,eAAe;AAEjC,UAAI,KAAK,OAAO,QAAQ;AACtB,gBAAQ,IAAI,MAAM,OAAO,oCAAoC,CAAC;AAC9D,uBAAe,QAAQ,CAAC,SAAS;AAC/B,iBAAO,aAAa,KAAK;AAAA,YACvB,UAAU,KAAK;AAAA,YACf,kBAAkB,KAAK;AAAA,YACvB,UAAU;AAAA,YACV,kBAAkB;AAAA,UACpB,CAAC;AAAA,QACH,CAAC;AACD,eAAO,WAAW,eAAe;AACjC,eAAO;AAAA,MACT;AAGA,YAAM,aAAa,MAAM,KAAK,aAAa,QAAQ;AACnD,cAAQ;AAAA,QACN,MAAM,KAAK,gBAAgB,WAAW,IAAI,KAAK,WAAW,GAAG,GAAG;AAAA,MAClE;AAGA,YAAM,YAAY,KAAK,OAAO,aAAa;AAC3C,YAAM,UAAU,KAAK,OAAO,WAAW;AAEvC,eAAS,IAAI,GAAG,IAAI,eAAe,QAAQ,KAAK,WAAW;AACzD,cAAM,QAAQ,eAAe,MAAM,GAAG,IAAI,SAAS;AACnD,gBAAQ;AAAA,UACN,MAAM;AAAA,YACJ,oBAAoB,KAAK,MAAM,IAAI,SAAS,IAAI,CAAC,IAAI,KAAK,KAAK,eAAe,SAAS,SAAS,CAAC;AAAA,UACnG;AAAA,QACF;AAEA,mBAAW,QAAQ,OAAO;AACxB,cAAI;AACF,kBAAM,UAAU,MAAM,KAAK,YAAY,MAAM,WAAW,EAAE;AAC1D,kBAAM,UAAU;AAAA,cACd,UAAU,KAAK;AAAA,cACf,kBAAkB,KAAK;AAAA,cACvB,UAAU,QAAQ;AAAA,cAClB,kBAAkB,QAAQ;AAAA,cAC1B,SAAS;AAAA,YACX;AAEA,mBAAO;AACP,oBAAQ;AAAA,cACN,MAAM;AAAA,gBACJ,GAAG,KAAK,UAAU,WAAM,QAAQ,UAAU,KAAK,KAAK,KAAK;AAAA,cAC3D;AAAA,YACF;AAGA,gBAAI,KAAK,OAAO,kBAAkB;AAChC,kBAAI;AACF,sBAAM,KAAK,WAAW,KAAK,EAAE;AAC7B,wBAAQ,UAAU;AAClB,uBAAO;AACP,wBAAQ;AAAA,kBACN,MAAM,KAAK,WAAW,KAAK,UAAU,cAAc;AAAA,gBACrD;AAAA,cACF,SAAS,aAAsB;AAC7B,uBAAO;AACP,uBAAO,OAAO;AAAA,kBACZ,qBAAqB,KAAK,UAAU,KAAM,YAAsB,OAAO;AAAA,gBACzE;AACA,wBAAQ;AAAA,kBACN,MAAM;AAAA,oBACJ,oBAAoB,KAAK,UAAU,iBAAkB,YAAsB,OAAO;AAAA,kBACpF;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAEA,mBAAO,aAAa,KAAK,OAAO;AAAA,UAClC,SAAS,OAAgB;AACvB,kBAAM,WAAY,MAAgB;AAClC,mBAAO,OAAO,KAAK,GAAG,KAAK,UAAU,KAAK,QAAQ,EAAE;AACpD,mBAAO,aAAa,KAAK;AAAA,cACvB,UAAU,KAAK;AAAA,cACf,kBAAkB,KAAK;AAAA,cACvB,OAAO;AAAA,YACT,CAAC;AACD,mBAAO;AACP,oBAAQ,IAAI,MAAM,IAAI,GAAG,KAAK,UAAU,KAAK,QAAQ,EAAE,CAAC;AAAA,UAC1D;AAAA,QACF;AAGA,YAAI,IAAI,YAAY,eAAe,QAAQ;AACzC,kBAAQ,IAAI,MAAM,KAAK,WAAW,OAAO,yBAAyB,CAAC;AACnE,gBAAM,KAAK,MAAM,OAAO;AAAA,QAC1B;AAAA,MACF;AAAA,IACF,SAAS,OAAgB;AACvB,aAAO,OAAO,KAAK,qBAAsB,MAAgB,OAAO,EAAE;AAClE,aAAO,MAAM,qBAAqB,KAAc;AAAA,IAClD;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,YACZ,YACA,cACc;AAEd,UAAM,eAAuC;AAAA,MAC3C,SAAS;AAAA,MACT,WAAW;AAAA,MACX,SAAS;AAAA,MACT,WAAW;AAAA,MACX,UAAU;AAAA,IACZ;AAGA,UAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBxB,UAAM,YAAY;AAAA,MAChB,OAAO,cAAc,WAAW,KAAK;AAAA,MACrC,aAAa,KAAK,0BAA0B,UAAU;AAAA,MACtD,QAAQ;AAAA,MACR,UAAU,KAAK,YAAY,WAAW,QAAQ;AAAA,IAChD;AAEA,UAAM,WAAW,MAAM,KAAK,aAAa,YAOtC,iBAAiB,EAAE,OAAO,UAAU,CAAC;AAExC,QAAI,CAAC,SAAS,MAAM,aAAa,SAAS;AACxC,YAAM,IAAI;AAAA,QACR;AAAA,QACA,UAAU;AAAA,QACV,EAAE,cAAc,WAAW,IAAI,kBAAkB,WAAW,WAAW;AAAA,MACzE;AAAA,IACF;AAEA,WAAO,SAAS,KAAK,YAAY;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKQ,0BAA0B,YAAyB;AACzD,QAAI,cAAc,WAAW,eAAe;AAE5C,mBAAe;AAAA;AAAA;AAAA;AAAA;AACf,mBAAe,kBAAkB,WAAW,UAAU;AAAA;AACtD,mBAAe,gBAAe,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA;AACtD,mBAAe,qBAAqB,WAAW,MAAM,IAAI;AAAA;AAEzD,QAAI,WAAW,UAAU;AACvB,qBAAe,wBAAwB,WAAW,SAAS,IAAI;AAAA;AAAA,IACjE;AAEA,QAAI,WAAW,UAAU;AACvB,qBAAe,wBAAwB,WAAW,QAAQ;AAAA;AAAA,IAC5D;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,YAAY,UAA2B;AAE7C,WAAO,YAAY;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,WAAW,QAA+B;AACtD,UAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQpB,UAAM,WAAW,MAAO,KAAK,aAAqB,YAAY,aAAa;AAAA,MACzE,IAAI;AAAA,IACN,CAAC;AAED,QAAI,CAAC,SAAS,MAAM,aAAa,SAAS;AACxC,YAAM,IAAI;AAAA,QACR;AAAA,QACA,UAAU;AAAA,QACV,EAAE,OAAO;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,MAAM,IAA2B;AACvC,WAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA,EACzD;AACF;AAKA,eAAsB,aAAa,QAAwC;AACzE,QAAM,WAAW,IAAI,eAAe,MAAM;AAE1C,UAAQ,IAAI,MAAM,KAAK,wBAAwB,CAAC;AAChD,QAAM,iBAAiB,MAAM,SAAS,gBAAgB;AAEtD,MAAI,CAAC,eAAe,OAAO,SAAS;AAClC,YAAQ;AAAA,MACN,MAAM,IAAI,6BAA6B,eAAe,OAAO,KAAK,EAAE;AAAA,IACtE;AACA;AAAA,EACF;AAEA,MAAI,CAAC,eAAe,OAAO,SAAS;AAClC,YAAQ;AAAA,MACN,MAAM,IAAI,6BAA6B,eAAe,OAAO,KAAK,EAAE;AAAA,IACtE;AACA;AAAA,EACF;AAEA,UAAQ,IAAI,MAAM,MAAM,6BAA6B,CAAC;AACtD,UAAQ;AAAA,IACN,MAAM;AAAA,MACJ,WAAW,eAAe,OAAO,KAAK,KAAK,IAAI,MAAM,eAAe,OAAO,KAAK,KAAK,IAAI;AAAA,IAC3F;AAAA,EACF;AACA,UAAQ;AAAA,IACN,MAAM;AAAA,MACJ,WAAW,eAAe,OAAO,KAAK,KAAK,IAAI,MAAM,eAAe,OAAO,KAAK,KAAK,IAAI;AAAA,IAC3F;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,SAAS,QAAQ;AAEtC,UAAQ,IAAI,MAAM,KAAK,sBAAsB,CAAC;AAC9C,UAAQ,IAAI,kBAAkB,OAAO,UAAU,EAAE;AACjD,UAAQ,IAAI,eAAe,OAAO,QAAQ,EAAE;AAC5C,UAAQ,IAAI,MAAM,MAAM,eAAe,OAAO,QAAQ,EAAE,CAAC;AACzD,UAAQ,IAAI,MAAM,IAAI,aAAa,OAAO,MAAM,EAAE,CAAC;AACnD,MAAI,OAAO,kBAAkB;AAC3B,YAAQ,IAAI,MAAM,KAAK,cAAc,OAAO,OAAO,EAAE,CAAC;AACtD,QAAI,OAAO,eAAe,GAAG;AAC3B,cAAQ,IAAI,MAAM,OAAO,oBAAoB,OAAO,YAAY,EAAE,CAAC;AAAA,IACrE;AAAA,EACF;AAEA,MAAI,OAAO,OAAO,SAAS,GAAG;AAC5B,YAAQ,IAAI,MAAM,IAAI,WAAW,CAAC;AAClC,WAAO,OAAO,QAAQ,CAAC,UAAU,QAAQ,IAAI,MAAM,IAAI,OAAO,KAAK,EAAE,CAAC,CAAC;AAAA,EACzE;AAEA,MAAI,OAAO,WAAW,GAAG;AACvB,YAAQ;AAAA,MACN,MAAM;AAAA,QACJ;AAAA,uBAA0B,OAAO,QAAQ;AAAA,MAC3C;AAAA,IACF;AACA,QAAI,OAAO,oBAAoB,OAAO,UAAU,GAAG;AACjD,cAAQ;AAAA,QACN,MAAM,KAAK,MAAM,OAAO,OAAO,uCAAuC;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -7,11 +7,15 @@ import { URL } from "url";
7
7
  import { logger } from "../../core/monitoring/logger.js";
8
8
  import { LinearAuthManager } from "./auth.js";
9
9
  import chalk from "chalk";
10
+ import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
10
11
  function getEnv(key, defaultValue) {
11
12
  const value = process.env[key];
12
13
  if (value === void 0) {
13
14
  if (defaultValue !== void 0) return defaultValue;
14
- throw new Error(`Environment variable ${key} is required`);
15
+ throw new IntegrationError(
16
+ `Environment variable ${key} is required`,
17
+ ErrorCode.LINEAR_AUTH_FAILED
18
+ );
15
19
  }
16
20
  return value;
17
21
  }
@@ -49,10 +53,12 @@ class LinearOAuthServer {
49
53
  const { code, state, error, error_description } = req.query;
50
54
  if (error) {
51
55
  logger.error(`OAuth error: ${error} - ${error_description}`);
52
- res.send(this.generateErrorPage(
53
- "Authorization Failed",
54
- `${error}: ${error_description || "An error occurred during authorization"}`
55
- ));
56
+ res.send(
57
+ this.generateErrorPage(
58
+ "Authorization Failed",
59
+ `${error}: ${error_description || "An error occurred during authorization"}`
60
+ )
61
+ );
56
62
  if (state && this.authCompleteCallbacks.has(state)) {
57
63
  this.authCompleteCallbacks.get(state)(false);
58
64
  this.authCompleteCallbacks.delete(state);
@@ -61,20 +67,28 @@ class LinearOAuthServer {
61
67
  return;
62
68
  }
63
69
  if (!code) {
64
- res.send(this.generateErrorPage(
65
- "Missing Authorization Code",
66
- "No authorization code was provided in the callback"
67
- ));
70
+ res.send(
71
+ this.generateErrorPage(
72
+ "Missing Authorization Code",
73
+ "No authorization code was provided in the callback"
74
+ )
75
+ );
68
76
  this.scheduleShutdown();
69
77
  return;
70
78
  }
71
79
  try {
72
80
  const codeVerifier = state ? this.pendingCodeVerifiers.get(state) : process.env["_LINEAR_CODE_VERIFIER"];
73
81
  if (!codeVerifier) {
74
- throw new Error("Code verifier not found. Please restart the authorization process.");
82
+ throw new IntegrationError(
83
+ "Code verifier not found. Please restart the authorization process.",
84
+ ErrorCode.LINEAR_AUTH_FAILED
85
+ );
75
86
  }
76
87
  logger.info("Exchanging authorization code for tokens...");
77
- await this.authManager.exchangeCodeForToken(code, codeVerifier);
88
+ await this.authManager.exchangeCodeForToken(
89
+ code,
90
+ codeVerifier
91
+ );
78
92
  if (state) {
79
93
  this.pendingCodeVerifiers.delete(state);
80
94
  }
@@ -84,7 +98,10 @@ class LinearOAuthServer {
84
98
  res.send(this.generateSuccessPage());
85
99
  logger.info("Linear OAuth authentication completed successfully!");
86
100
  } else {
87
- throw new Error("Failed to verify Linear connection");
101
+ throw new IntegrationError(
102
+ "Failed to verify Linear connection",
103
+ ErrorCode.LINEAR_AUTH_FAILED
104
+ );
88
105
  }
89
106
  if (state && this.authCompleteCallbacks.has(state)) {
90
107
  this.authCompleteCallbacks.get(state)(true);
@@ -93,10 +110,12 @@ class LinearOAuthServer {
93
110
  this.scheduleShutdown();
94
111
  } catch (error2) {
95
112
  logger.error("Failed to complete OAuth flow:", error2);
96
- res.send(this.generateErrorPage(
97
- "Authentication Failed",
98
- error2.message
99
- ));
113
+ res.send(
114
+ this.generateErrorPage(
115
+ "Authentication Failed",
116
+ error2.message
117
+ )
118
+ );
100
119
  if (state && this.authCompleteCallbacks.has(state)) {
101
120
  this.authCompleteCallbacks.get(state)(false);
102
121
  this.authCompleteCallbacks.delete(state);
@@ -108,10 +127,12 @@ class LinearOAuthServer {
108
127
  try {
109
128
  const config = this.authManager.loadConfig();
110
129
  if (!config) {
111
- res.status(400).send(this.generateErrorPage(
112
- "Configuration Missing",
113
- "Linear OAuth configuration not found. Please configure your client ID and secret."
114
- ));
130
+ res.status(400).send(
131
+ this.generateErrorPage(
132
+ "Configuration Missing",
133
+ "Linear OAuth configuration not found. Please configure your client ID and secret."
134
+ )
135
+ );
115
136
  return;
116
137
  }
117
138
  const state = this.generateState();
@@ -120,10 +141,12 @@ class LinearOAuthServer {
120
141
  res.redirect(url);
121
142
  } catch (error) {
122
143
  logger.error("Failed to start OAuth flow:", error);
123
- res.status(500).send(this.generateErrorPage(
124
- "OAuth Start Failed",
125
- error.message
126
- ));
144
+ res.status(500).send(
145
+ this.generateErrorPage(
146
+ "OAuth Start Failed",
147
+ error.message
148
+ )
149
+ );
127
150
  }
128
151
  });
129
152
  this.app.use((req, res) => {
@@ -277,7 +300,7 @@ class LinearOAuthServer {
277
300
  const response = await fetch("https://api.linear.app/graphql", {
278
301
  method: "POST",
279
302
  headers: {
280
- "Authorization": `Bearer ${token}`,
303
+ Authorization: `Bearer ${token}`,
281
304
  "Content-Type": "application/json"
282
305
  },
283
306
  body: JSON.stringify({
@@ -287,7 +310,9 @@ class LinearOAuthServer {
287
310
  if (response.ok) {
288
311
  const result = await response.json();
289
312
  if (result.data?.viewer) {
290
- logger.info(`Connected to Linear as: ${result.data.viewer.name} (${result.data.viewer.email})`);
313
+ logger.info(
314
+ `Connected to Linear as: ${result.data.viewer.name} (${result.data.viewer.email})`
315
+ );
291
316
  return true;
292
317
  }
293
318
  }
@@ -315,16 +340,26 @@ class LinearOAuthServer {
315
340
  this.config.port,
316
341
  this.config.host,
317
342
  () => {
318
- console.log(chalk.green("\u2713") + chalk.bold(" Linear OAuth Server Started"));
343
+ console.log(
344
+ chalk.green("\u2713") + chalk.bold(" Linear OAuth Server Started")
345
+ );
319
346
  console.log(chalk.cyan(" Authorization URL: ") + setupUrl);
320
- console.log(chalk.cyan(" Callback URL: ") + `http://${this.config.host}:${this.config.port}${this.config.redirectPath}`);
347
+ console.log(
348
+ chalk.cyan(" Callback URL: ") + `http://${this.config.host}:${this.config.port}${this.config.redirectPath}`
349
+ );
321
350
  console.log("");
322
351
  console.log(chalk.yellow(" \u26A0 Configuration Required:"));
323
- console.log(" 1. Create a Linear OAuth app at: https://linear.app/settings/api");
324
- console.log(` 2. Set redirect URI to: http://${this.config.host}:${this.config.port}${this.config.redirectPath}`);
352
+ console.log(
353
+ " 1. Create a Linear OAuth app at: https://linear.app/settings/api"
354
+ );
355
+ console.log(
356
+ ` 2. Set redirect URI to: http://${this.config.host}:${this.config.port}${this.config.redirectPath}`
357
+ );
325
358
  console.log(" 3. Set environment variables:");
326
359
  console.log(' export LINEAR_CLIENT_ID="your_client_id"');
327
- console.log(' export LINEAR_CLIENT_SECRET="your_client_secret"');
360
+ console.log(
361
+ ' export LINEAR_CLIENT_SECRET="your_client_secret"'
362
+ );
328
363
  console.log(" 4. Restart the auth process");
329
364
  resolve({ url: setupUrl });
330
365
  }
@@ -338,17 +373,25 @@ class LinearOAuthServer {
338
373
  this.config.port,
339
374
  this.config.host,
340
375
  () => {
341
- console.log(chalk.green("\u2713") + chalk.bold(" Linear OAuth Server Started"));
376
+ console.log(
377
+ chalk.green("\u2713") + chalk.bold(" Linear OAuth Server Started")
378
+ );
342
379
  console.log(chalk.cyan(" Open this URL in your browser:"));
343
380
  console.log(" " + chalk.underline(url));
344
381
  console.log("");
345
- console.log(chalk.gray(" The server will automatically shut down after authorization completes."));
382
+ console.log(
383
+ chalk.gray(
384
+ " The server will automatically shut down after authorization completes."
385
+ )
386
+ );
346
387
  resolve({ url, codeVerifier });
347
388
  }
348
389
  );
349
390
  this.authCompleteCallbacks.set(state, (success) => {
350
391
  if (success) {
351
- console.log(chalk.green("\n\u2713 Linear authorization completed successfully!"));
392
+ console.log(
393
+ chalk.green("\n\u2713 Linear authorization completed successfully!")
394
+ );
352
395
  } else {
353
396
  console.log(chalk.red("\n\u2717 Linear authorization failed"));
354
397
  }