cyrus-ai 0.1.35-alpha.2 → 0.1.37

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/app.js CHANGED
@@ -1,23 +1,23 @@
1
1
  #!/usr/bin/env node
2
- import { EdgeWorker, SharedApplicationServer } from 'cyrus-edge-worker';
3
- import dotenv from 'dotenv';
4
- import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync } from 'fs';
5
- import { resolve, dirname, basename } from 'path';
6
- import open from 'open';
7
- import readline from 'readline';
8
- import http from 'http';
9
- import { homedir } from 'os';
2
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
3
+ import http from "node:http";
4
+ import { homedir } from "node:os";
5
+ import { basename, dirname, resolve } from "node:path";
6
+ import readline from "node:readline";
7
+ import { EdgeWorker, SharedApplicationServer, } from "cyrus-edge-worker";
8
+ import dotenv from "dotenv";
9
+ import open from "open";
10
10
  // Parse command line arguments
11
11
  const args = process.argv.slice(2);
12
- const envFileArg = args.find(arg => arg.startsWith('--env-file='));
12
+ const envFileArg = args.find((arg) => arg.startsWith("--env-file="));
13
13
  // Note: __dirname removed since version is now hardcoded
14
14
  // Handle --version argument
15
- if (args.includes('--version')) {
16
- console.log('0.1.28');
15
+ if (args.includes("--version")) {
16
+ console.log("0.1.37");
17
17
  process.exit(0);
18
18
  }
19
19
  // Handle --help argument
20
- if (args.includes('--help') || args.includes('-h')) {
20
+ if (args.includes("--help") || args.includes("-h")) {
21
21
  console.log(`
22
22
  cyrus - AI-powered Linear issue automation using Claude
23
23
 
@@ -42,7 +42,7 @@ Examples:
42
42
  }
43
43
  // Load environment variables only if --env-file is specified
44
44
  if (envFileArg) {
45
- const envFile = envFileArg.split('=')[1];
45
+ const envFile = envFileArg.split("=")[1];
46
46
  if (envFile) {
47
47
  dotenv.config({ path: envFile });
48
48
  }
@@ -57,13 +57,13 @@ class EdgeApp {
57
57
  * Get the edge configuration file path
58
58
  */
59
59
  getEdgeConfigPath() {
60
- return resolve(homedir(), '.cyrus', 'config.json');
60
+ return resolve(homedir(), ".cyrus", "config.json");
61
61
  }
62
62
  /**
63
63
  * Get the legacy edge configuration file path (for migration)
64
64
  */
65
65
  getLegacyEdgeConfigPath() {
66
- return resolve(process.cwd(), '.edge-config.json');
66
+ return resolve(process.cwd(), ".edge-config.json");
67
67
  }
68
68
  /**
69
69
  * Migrate configuration from legacy location if needed
@@ -106,15 +106,15 @@ class EdgeApp {
106
106
  let config = { repositories: [] };
107
107
  if (existsSync(edgeConfigPath)) {
108
108
  try {
109
- config = JSON.parse(readFileSync(edgeConfigPath, 'utf-8'));
109
+ config = JSON.parse(readFileSync(edgeConfigPath, "utf-8"));
110
110
  }
111
111
  catch (e) {
112
- console.error('Failed to load edge config:', e.message);
112
+ console.error("Failed to load edge config:", e.message);
113
113
  }
114
114
  }
115
115
  // Strip promptTemplatePath from all repositories to ensure built-in template is used
116
116
  if (config.repositories) {
117
- config.repositories = config.repositories.map(repo => {
117
+ config.repositories = config.repositories.map((repo) => {
118
118
  const { promptTemplatePath, ...repoWithoutTemplate } = repo;
119
119
  if (promptTemplatePath) {
120
120
  console.log(`Ignoring custom prompt template for repository: ${repo.name} (using built-in template)`);
@@ -142,66 +142,81 @@ class EdgeApp {
142
142
  async setupRepositoryWizard(linearCredentials) {
143
143
  const rl = readline.createInterface({
144
144
  input: process.stdin,
145
- output: process.stdout
145
+ output: process.stdout,
146
146
  });
147
147
  const question = (prompt) => new Promise((resolve) => {
148
148
  rl.question(prompt, resolve);
149
149
  });
150
- console.log('\nšŸ“ Repository Setup');
151
- console.log('─'.repeat(50));
150
+ console.log("\nšŸ“ Repository Setup");
151
+ console.log("─".repeat(50));
152
152
  try {
153
153
  // Ask for repository details
154
- const repositoryPath = await question(`Repository path (default: ${process.cwd()}): `) || process.cwd();
155
- const repositoryName = await question(`Repository name (default: ${basename(repositoryPath)}): `) || basename(repositoryPath);
156
- const baseBranch = await question('Base branch (default: main): ') || 'main';
154
+ const repositoryPath = (await question(`Repository path (default: ${process.cwd()}): `)) ||
155
+ process.cwd();
156
+ const repositoryName = (await question(`Repository name (default: ${basename(repositoryPath)}): `)) || basename(repositoryPath);
157
+ const baseBranch = (await question("Base branch (default: main): ")) || "main";
157
158
  // Create a path-safe version of the repository name for namespacing
158
- const repoNameSafe = repositoryName.replace(/[^a-zA-Z0-9-_]/g, '-').toLowerCase();
159
- const defaultWorkspaceDir = resolve(homedir(), '.cyrus', 'workspaces', repoNameSafe);
160
- const workspaceBaseDir = await question(`Workspace directory (default: ${defaultWorkspaceDir}): `) || defaultWorkspaceDir;
159
+ const repoNameSafe = repositoryName
160
+ .replace(/[^a-zA-Z0-9-_]/g, "-")
161
+ .toLowerCase();
162
+ const defaultWorkspaceDir = resolve(homedir(), ".cyrus", "workspaces", repoNameSafe);
163
+ const workspaceBaseDir = (await question(`Workspace directory (default: ${defaultWorkspaceDir}): `)) || defaultWorkspaceDir;
161
164
  // Note: Prompt template is now hardcoded - no longer configurable
162
165
  // Ask for MCP configuration
163
- console.log('\nšŸ”§ MCP (Model Context Protocol) Configuration');
164
- console.log('MCP allows Claude to access external tools and data sources.');
165
- console.log('Examples: filesystem access, database connections, API integrations');
166
- console.log('See: https://docs.anthropic.com/en/docs/claude-code/mcp');
167
- console.log('');
166
+ console.log("\nšŸ”§ MCP (Model Context Protocol) Configuration");
167
+ console.log("MCP allows Claude to access external tools and data sources.");
168
+ console.log("Examples: filesystem access, database connections, API integrations");
169
+ console.log("See: https://docs.anthropic.com/en/docs/claude-code/mcp");
170
+ console.log("");
168
171
  const mcpConfigInput = await question('MCP config file path (optional, format: {"mcpServers": {...}}, e.g., ./mcp-config.json): ');
169
172
  const mcpConfigPath = mcpConfigInput.trim() || undefined;
170
173
  // Ask for allowed tools configuration
171
- console.log('\nšŸ”§ Tool Configuration');
172
- console.log('Available tools: Read(**),Edit(**),Bash,Task,WebFetch,WebSearch,TodoRead,TodoWrite,NotebookRead,NotebookEdit,Batch');
173
- console.log('');
174
- console.log('āš ļø SECURITY NOTE: Bash tool requires special configuration for safety:');
174
+ console.log("\nšŸ”§ Tool Configuration");
175
+ console.log("Available tools: Read(**),Edit(**),Bash,Task,WebFetch,WebSearch,TodoRead,TodoWrite,NotebookRead,NotebookEdit,Batch");
176
+ console.log("");
177
+ console.log("āš ļø SECURITY NOTE: Bash tool requires special configuration for safety:");
175
178
  console.log(' • Use "Bash" for full access (not recommended in production)');
176
179
  console.log(' • Use "Bash(npm:*)" to restrict to npm commands only');
177
180
  console.log(' • Use "Bash(git:*)" to restrict to git commands only');
178
- console.log(' • See: https://docs.anthropic.com/en/docs/claude-code/settings#permissions');
179
- console.log('');
180
- console.log('Default: All tools except Bash (leave blank for all non-Bash tools)');
181
- const allowedToolsInput = await question('Allowed tools (comma-separated, default: all except Bash): ');
182
- const allowedTools = allowedToolsInput ? allowedToolsInput.split(',').map(t => t.trim()) : undefined;
181
+ console.log(" • See: https://docs.anthropic.com/en/docs/claude-code/settings#permissions");
182
+ console.log("");
183
+ console.log("Default: All tools except Bash (leave blank for all non-Bash tools)");
184
+ const allowedToolsInput = await question("Allowed tools (comma-separated, default: all except Bash): ");
185
+ const allowedTools = allowedToolsInput
186
+ ? allowedToolsInput.split(",").map((t) => t.trim())
187
+ : undefined;
183
188
  // Ask for team keys configuration
184
- console.log('\nšŸ·ļø Team-Based Routing (Optional)');
185
- console.log('Configure specific Linear team keys to route issues to this repository.');
186
- console.log('Example: CEE,FRONT,BACK for teams with those prefixes');
187
- console.log('Leave blank to receive all issues from the workspace.');
188
- const teamKeysInput = await question('Team keys (comma-separated, optional): ');
189
- const teamKeys = teamKeysInput ? teamKeysInput.split(',').map(t => t.trim().toUpperCase()) : undefined;
189
+ console.log("\nšŸ·ļø Team-Based Routing (Optional)");
190
+ console.log("Configure specific Linear team keys to route issues to this repository.");
191
+ console.log("Example: CEE,FRONT,BACK for teams with those prefixes");
192
+ console.log("Leave blank to receive all issues from the workspace.");
193
+ const teamKeysInput = await question("Team keys (comma-separated, optional): ");
194
+ const teamKeys = teamKeysInput
195
+ ? teamKeysInput.split(",").map((t) => t.trim().toUpperCase())
196
+ : undefined;
190
197
  // Ask for label-based system prompt configuration
191
- console.log('\nšŸŽÆ Label-Based System Prompts (Optional)');
192
- console.log('Cyrus can use different strategies based on Linear issue labels.');
193
- console.log('Configure which labels trigger each specialized mode:');
194
- console.log('• Debugger mode: Focuses on systematic problem investigation');
195
- console.log('• Builder mode: Emphasizes feature implementation and code quality');
196
- console.log('• Scoper mode: Helps analyze requirements and create technical plans');
198
+ console.log("\nšŸŽÆ Label-Based System Prompts (Optional)");
199
+ console.log("Cyrus can use different strategies based on Linear issue labels.");
200
+ console.log("Configure which labels trigger each specialized mode:");
201
+ console.log("• Debugger mode: Focuses on systematic problem investigation");
202
+ console.log("• Builder mode: Emphasizes feature implementation and code quality");
203
+ console.log("• Scoper mode: Helps analyze requirements and create technical plans");
197
204
  const debuggerLabelsInput = await question('Labels for debugger mode (comma-separated, e.g., "Bug"): ');
198
205
  const builderLabelsInput = await question('Labels for builder mode (comma-separated, e.g., "Feature,Improvement"): ');
199
206
  const scoperLabelsInput = await question('Labels for scoper mode (comma-separated, e.g., "PRD"): ');
200
- const labelPrompts = (debuggerLabelsInput || builderLabelsInput || scoperLabelsInput) ? {
201
- ...(debuggerLabelsInput && { debugger: debuggerLabelsInput.split(',').map(l => l.trim()) }),
202
- ...(builderLabelsInput && { builder: builderLabelsInput.split(',').map(l => l.trim()) }),
203
- ...(scoperLabelsInput && { scoper: scoperLabelsInput.split(',').map(l => l.trim()) })
204
- } : undefined;
207
+ const labelPrompts = debuggerLabelsInput || builderLabelsInput || scoperLabelsInput
208
+ ? {
209
+ ...(debuggerLabelsInput && {
210
+ debugger: debuggerLabelsInput.split(",").map((l) => l.trim()),
211
+ }),
212
+ ...(builderLabelsInput && {
213
+ builder: builderLabelsInput.split(",").map((l) => l.trim()),
214
+ }),
215
+ ...(scoperLabelsInput && {
216
+ scoper: scoperLabelsInput.split(",").map((l) => l.trim()),
217
+ }),
218
+ }
219
+ : undefined;
205
220
  rl.close();
206
221
  // Create repository configuration
207
222
  const repository = {
@@ -216,7 +231,7 @@ class EdgeApp {
216
231
  ...(allowedTools && { allowedTools }),
217
232
  ...(mcpConfigPath && { mcpConfigPath: resolve(mcpConfigPath) }),
218
233
  ...(teamKeys && { teamKeys }),
219
- ...(labelPrompts && { labelPrompts })
234
+ ...(labelPrompts && { labelPrompts }),
220
235
  };
221
236
  return repository;
222
237
  }
@@ -246,7 +261,9 @@ class EdgeApp {
246
261
  }
247
262
  else {
248
263
  // Create temporary SharedApplicationServer for OAuth flow during initial setup
249
- const serverPort = process.env.CYRUS_SERVER_PORT ? parseInt(process.env.CYRUS_SERVER_PORT, 10) : 3456;
264
+ const serverPort = process.env.CYRUS_SERVER_PORT
265
+ ? parseInt(process.env.CYRUS_SERVER_PORT, 10)
266
+ : 3456;
250
267
  const tempServer = new SharedApplicationServer(serverPort);
251
268
  try {
252
269
  // Start the server
@@ -267,7 +284,7 @@ class EdgeApp {
267
284
  return {
268
285
  linearToken: result.linearToken,
269
286
  linearWorkspaceId: result.linearWorkspaceId,
270
- linearWorkspaceName: result.linearWorkspaceName
287
+ linearWorkspaceName: result.linearWorkspaceName,
271
288
  };
272
289
  }
273
290
  finally {
@@ -300,7 +317,7 @@ class EdgeApp {
300
317
  console.log(``);
301
318
  const rl = readline.createInterface({
302
319
  input: process.stdin,
303
- output: process.stdout
320
+ output: process.stdout,
304
321
  });
305
322
  return new Promise((resolve) => {
306
323
  rl.question(`Enter your ngrok auth token (or press Enter to skip): `, async (token) => {
@@ -327,7 +344,7 @@ class EdgeApp {
327
344
  /**
328
345
  * Start the EdgeWorker with given configuration
329
346
  */
330
- async startEdgeWorker({ proxyUrl, repositories }) {
347
+ async startEdgeWorker({ proxyUrl, repositories, }) {
331
348
  // Get ngrok auth token (prompt if needed and not external host)
332
349
  let ngrokAuthToken;
333
350
  if (process.env.CYRUS_HOST_EXTERNAL !== "true") {
@@ -338,13 +355,15 @@ class EdgeApp {
338
355
  const config = {
339
356
  proxyUrl,
340
357
  repositories,
341
- defaultAllowedTools: process.env.ALLOWED_TOOLS?.split(",").map(t => t.trim()) || [],
358
+ defaultAllowedTools: process.env.ALLOWED_TOOLS?.split(",").map((t) => t.trim()) || [],
342
359
  webhookBaseUrl: process.env.CYRUS_BASE_URL,
343
- serverPort: process.env.CYRUS_SERVER_PORT ? parseInt(process.env.CYRUS_SERVER_PORT, 10) : 3456,
360
+ serverPort: process.env.CYRUS_SERVER_PORT
361
+ ? parseInt(process.env.CYRUS_SERVER_PORT, 10)
362
+ : 3456,
344
363
  serverHost: process.env.CYRUS_HOST_EXTERNAL === "true" ? "0.0.0.0" : "localhost",
345
364
  ngrokAuthToken,
346
365
  features: {
347
- enableContinuation: true
366
+ enableContinuation: true,
348
367
  },
349
368
  handlers: {
350
369
  createWorkspace: async (issue, repository) => {
@@ -354,7 +373,7 @@ class EdgeApp {
354
373
  const linearCredentials = {
355
374
  linearToken: token,
356
375
  linearWorkspaceId: workspaceId,
357
- linearWorkspaceName: workspaceName
376
+ linearWorkspaceName: workspaceName,
358
377
  };
359
378
  // Handle OAuth completion for repository setup
360
379
  if (this.edgeWorker) {
@@ -363,33 +382,36 @@ class EdgeApp {
363
382
  try {
364
383
  const newRepo = await this.setupRepositoryWizard(linearCredentials);
365
384
  // Add to existing repositories
366
- let edgeConfig = this.loadEdgeConfig();
385
+ const edgeConfig = this.loadEdgeConfig();
367
386
  console.log(`šŸ“Š Current config has ${edgeConfig.repositories?.length || 0} repositories`);
368
- edgeConfig.repositories = [...(edgeConfig.repositories || []), newRepo];
387
+ edgeConfig.repositories = [
388
+ ...(edgeConfig.repositories || []),
389
+ newRepo,
390
+ ];
369
391
  console.log(`šŸ“Š Adding repository "${newRepo.name}", new total: ${edgeConfig.repositories.length}`);
370
392
  this.saveEdgeConfig(edgeConfig);
371
- console.log('\nāœ… Repository configured successfully!');
372
- console.log('šŸ“ ~/.cyrus/config.json file has been updated with your new repository configuration.');
373
- console.log('šŸ’” You can edit this file and restart Cyrus at any time to modify settings.');
393
+ console.log("\nāœ… Repository configured successfully!");
394
+ console.log("šŸ“ ~/.cyrus/config.json file has been updated with your new repository configuration.");
395
+ console.log("šŸ’” You can edit this file and restart Cyrus at any time to modify settings.");
374
396
  // Restart edge worker with new config
375
397
  await this.edgeWorker.stop();
376
398
  this.edgeWorker = null;
377
399
  // Give a small delay to ensure file is written
378
- await new Promise(resolve => setTimeout(resolve, 100));
400
+ await new Promise((resolve) => setTimeout(resolve, 100));
379
401
  // Reload configuration and restart worker without going through setup
380
402
  const updatedConfig = this.loadEdgeConfig();
381
403
  console.log(`\nšŸ”„ Reloading with ${updatedConfig.repositories?.length || 0} repositories from config file`);
382
404
  return this.startEdgeWorker({
383
405
  proxyUrl,
384
- repositories: updatedConfig.repositories || []
406
+ repositories: updatedConfig.repositories || [],
385
407
  });
386
408
  }
387
409
  catch (error) {
388
- console.error('\nāŒ Repository setup failed:', error.message);
410
+ console.error("\nāŒ Repository setup failed:", error.message);
389
411
  }
390
412
  }
391
- }
392
- }
413
+ },
414
+ },
393
415
  };
394
416
  // Create and start EdgeWorker
395
417
  this.edgeWorker = new EdgeWorker(config);
@@ -397,10 +419,10 @@ class EdgeApp {
397
419
  this.setupEventHandlers();
398
420
  // Start the worker
399
421
  await this.edgeWorker.start();
400
- console.log('\nāœ… Edge worker started successfully');
422
+ console.log("\nāœ… Edge worker started successfully");
401
423
  console.log(`Configured proxy URL: ${config.proxyUrl}`);
402
424
  console.log(`Managing ${repositories.length} repositories:`);
403
- repositories.forEach(repo => {
425
+ repositories.forEach((repo) => {
404
426
  console.log(` - ${repo.name} (${repo.repositoryPath})`);
405
427
  });
406
428
  }
@@ -410,27 +432,28 @@ class EdgeApp {
410
432
  async start() {
411
433
  try {
412
434
  // Set proxy URL with default
413
- const proxyUrl = process.env.PROXY_URL || 'https://cyrus-proxy.ceedar.workers.dev';
435
+ const proxyUrl = process.env.PROXY_URL || "https://cyrus-proxy.ceedar.workers.dev";
414
436
  // No need to validate Claude CLI - using Claude TypeScript SDK now
415
437
  // Load edge configuration
416
- let edgeConfig = this.loadEdgeConfig();
438
+ const edgeConfig = this.loadEdgeConfig();
417
439
  let repositories = edgeConfig.repositories || [];
418
440
  // Check if we need to set up
419
441
  const needsSetup = repositories.length === 0;
420
- const hasLinearCredentials = repositories.some(r => r.linearToken) || process.env.LINEAR_OAUTH_TOKEN;
442
+ const hasLinearCredentials = repositories.some((r) => r.linearToken) ||
443
+ process.env.LINEAR_OAUTH_TOKEN;
421
444
  if (needsSetup) {
422
- console.log('šŸš€ Welcome to Cyrus Edge Worker!');
445
+ console.log("šŸš€ Welcome to Cyrus Edge Worker!");
423
446
  // Check if they want to use existing credentials or add new workspace
424
447
  let linearCredentials = null;
425
448
  if (hasLinearCredentials) {
426
449
  // Show available workspaces from existing repos
427
450
  const workspaces = new Map();
428
- for (const repo of (edgeConfig.repositories || [])) {
451
+ for (const repo of edgeConfig.repositories || []) {
429
452
  if (!workspaces.has(repo.linearWorkspaceId)) {
430
453
  workspaces.set(repo.linearWorkspaceId, {
431
454
  id: repo.linearWorkspaceId,
432
- name: 'Unknown Workspace',
433
- token: repo.linearToken
455
+ name: "Unknown Workspace",
456
+ token: repo.linearToken,
434
457
  });
435
458
  }
436
459
  }
@@ -441,24 +464,24 @@ class EdgeApp {
441
464
  linearCredentials = {
442
465
  linearToken: ws.token,
443
466
  linearWorkspaceId: ws.id,
444
- linearWorkspaceName: ws.name
467
+ linearWorkspaceName: ws.name,
445
468
  };
446
469
  console.log(`\nšŸ“‹ Using Linear workspace: ${linearCredentials.linearWorkspaceName}`);
447
470
  }
448
471
  }
449
472
  else if (workspaces.size > 1) {
450
473
  // Multiple workspaces, let user choose
451
- console.log('\nšŸ“‹ Available Linear workspaces:');
474
+ console.log("\nšŸ“‹ Available Linear workspaces:");
452
475
  const workspaceList = Array.from(workspaces.values());
453
476
  workspaceList.forEach((ws, i) => {
454
477
  console.log(`${i + 1}. ${ws.name}`);
455
478
  });
456
479
  const rl = readline.createInterface({
457
480
  input: process.stdin,
458
- output: process.stdout
481
+ output: process.stdout,
459
482
  });
460
- const choice = await new Promise(resolve => {
461
- rl.question('\nSelect workspace (number) or press Enter for new: ', resolve);
483
+ const choice = await new Promise((resolve) => {
484
+ rl.question("\nSelect workspace (number) or press Enter for new: ", resolve);
462
485
  });
463
486
  rl.close();
464
487
  const index = parseInt(choice) - 1;
@@ -468,7 +491,7 @@ class EdgeApp {
468
491
  linearCredentials = {
469
492
  linearToken: ws.token,
470
493
  linearWorkspaceId: ws.id,
471
- linearWorkspaceName: ws.name
494
+ linearWorkspaceName: ws.name,
472
495
  };
473
496
  console.log(`Using workspace: ${linearCredentials.linearWorkspaceName}`);
474
497
  }
@@ -482,56 +505,56 @@ class EdgeApp {
482
505
  // Use env vars
483
506
  linearCredentials = {
484
507
  linearToken: process.env.LINEAR_OAUTH_TOKEN,
485
- linearWorkspaceId: process.env.LINEAR_WORKSPACE_ID || 'unknown',
486
- linearWorkspaceName: 'Your Workspace'
508
+ linearWorkspaceId: process.env.LINEAR_WORKSPACE_ID || "unknown",
509
+ linearWorkspaceName: "Your Workspace",
487
510
  };
488
511
  }
489
512
  if (linearCredentials) {
490
- console.log('(OAuth server will start with EdgeWorker to connect additional workspaces)');
513
+ console.log("(OAuth server will start with EdgeWorker to connect additional workspaces)");
491
514
  }
492
515
  }
493
516
  else {
494
517
  // Get new Linear credentials
495
- console.log('\nšŸ“‹ Step 1: Connect to Linear');
496
- console.log('─'.repeat(50));
518
+ console.log("\nšŸ“‹ Step 1: Connect to Linear");
519
+ console.log("─".repeat(50));
497
520
  try {
498
521
  linearCredentials = await this.startOAuthFlow(proxyUrl);
499
- console.log('\nāœ… Linear connected successfully!');
522
+ console.log("\nāœ… Linear connected successfully!");
500
523
  }
501
524
  catch (error) {
502
- console.error('\nāŒ OAuth flow failed:', error.message);
503
- console.log('\nAlternatively, you can:');
504
- console.log('1. Visit', `${proxyUrl}/oauth/authorize`, 'in your browser');
505
- console.log('2. Copy the token after authorization');
506
- console.log('3. Add it to your .env.cyrus file as LINEAR_OAUTH_TOKEN');
525
+ console.error("\nāŒ OAuth flow failed:", error.message);
526
+ console.log("\nAlternatively, you can:");
527
+ console.log("1. Visit", `${proxyUrl}/oauth/authorize`, "in your browser");
528
+ console.log("2. Copy the token after authorization");
529
+ console.log("3. Add it to your .env.cyrus file as LINEAR_OAUTH_TOKEN");
507
530
  process.exit(1);
508
531
  }
509
532
  }
510
533
  if (!linearCredentials) {
511
- console.error('āŒ No Linear credentials available');
534
+ console.error("āŒ No Linear credentials available");
512
535
  process.exit(1);
513
536
  }
514
537
  // Now set up repository
515
- console.log('\nšŸ“‹ Step 2: Configure Repository');
516
- console.log('─'.repeat(50));
538
+ console.log("\nšŸ“‹ Step 2: Configure Repository");
539
+ console.log("─".repeat(50));
517
540
  try {
518
541
  const newRepo = await this.setupRepositoryWizard(linearCredentials);
519
542
  // Add to repositories
520
543
  repositories = [...(edgeConfig.repositories || []), newRepo];
521
544
  edgeConfig.repositories = repositories;
522
545
  this.saveEdgeConfig(edgeConfig);
523
- console.log('\nāœ… Repository configured successfully!');
524
- console.log('šŸ“ ~/.cyrus/config.json file has been updated with your repository configuration.');
525
- console.log('šŸ’” You can edit this file and restart Cyrus at any time to modify settings.');
546
+ console.log("\nāœ… Repository configured successfully!");
547
+ console.log("šŸ“ ~/.cyrus/config.json file has been updated with your repository configuration.");
548
+ console.log("šŸ’” You can edit this file and restart Cyrus at any time to modify settings.");
526
549
  // Ask if they want to add another
527
550
  const rl = readline.createInterface({
528
551
  input: process.stdin,
529
- output: process.stdout
552
+ output: process.stdout,
530
553
  });
531
- const addAnother = await new Promise(resolve => {
532
- rl.question('\nAdd another repository? (y/N): ', answer => {
554
+ const addAnother = await new Promise((resolve) => {
555
+ rl.question("\nAdd another repository? (y/N): ", (answer) => {
533
556
  rl.close();
534
- resolve(answer.toLowerCase() === 'y');
557
+ resolve(answer.toLowerCase() === "y");
535
558
  });
536
559
  });
537
560
  if (addAnother) {
@@ -540,14 +563,14 @@ class EdgeApp {
540
563
  }
541
564
  }
542
565
  catch (error) {
543
- console.error('\nāŒ Repository setup failed:', error.message);
566
+ console.error("\nāŒ Repository setup failed:", error.message);
544
567
  process.exit(1);
545
568
  }
546
569
  }
547
570
  // Validate we have repositories
548
571
  if (repositories.length === 0) {
549
- console.error('āŒ No repositories configured');
550
- console.log('\nUse the authorization link above to configure your first repository.');
572
+ console.error("āŒ No repositories configured");
573
+ console.log("\nUse the authorization link above to configure your first repository.");
551
574
  process.exit(1);
552
575
  }
553
576
  // Start the edge worker
@@ -558,48 +581,75 @@ class EdgeApp {
558
581
  console.log(`\nšŸ” OAuth server running on port ${serverPort}`);
559
582
  console.log(`šŸ‘‰ To authorize Linear (new workspace or re-auth):`);
560
583
  console.log(` ${proxyUrl}/oauth/authorize?callback=${oauthCallbackBaseUrl}/callback`);
561
- console.log('─'.repeat(70));
584
+ console.log("─".repeat(70));
562
585
  // Handle graceful shutdown
563
- process.on('SIGINT', () => this.shutdown());
564
- process.on('SIGTERM', () => this.shutdown());
586
+ process.on("SIGINT", () => this.shutdown());
587
+ process.on("SIGTERM", () => this.shutdown());
565
588
  // Handle uncaught exceptions and unhandled promise rejections
566
- process.on('uncaughtException', (error) => {
567
- console.error('🚨 Uncaught Exception:', error.message);
568
- console.error('Error type:', error.constructor.name);
569
- console.error('Stack:', error.stack);
570
- console.error('This error was caught by the global handler, preventing application crash');
589
+ process.on("uncaughtException", (error) => {
590
+ console.error("🚨 Uncaught Exception:", error.message);
591
+ console.error("Error type:", error.constructor.name);
592
+ console.error("Stack:", error.stack);
593
+ console.error("This error was caught by the global handler, preventing application crash");
571
594
  // Attempt graceful shutdown but don't wait indefinitely
572
595
  this.shutdown().finally(() => {
573
- console.error('Process exiting due to uncaught exception');
596
+ console.error("Process exiting due to uncaught exception");
574
597
  process.exit(1);
575
598
  });
576
599
  });
577
- process.on('unhandledRejection', (reason, promise) => {
578
- console.error('🚨 Unhandled Promise Rejection at:', promise);
579
- console.error('Reason:', reason);
580
- console.error('This rejection was caught by the global handler, continuing operation');
600
+ process.on("unhandledRejection", (reason, promise) => {
601
+ console.error("🚨 Unhandled Promise Rejection at:", promise);
602
+ console.error("Reason:", reason);
603
+ console.error("This rejection was caught by the global handler, continuing operation");
581
604
  // Log stack trace if reason is an Error
582
605
  if (reason instanceof Error && reason.stack) {
583
- console.error('Stack:', reason.stack);
606
+ console.error("Stack:", reason.stack);
584
607
  }
585
608
  // Log the error but don't exit the process for promise rejections
586
609
  // as they might be recoverable
587
610
  });
588
611
  }
589
612
  catch (error) {
590
- console.error('\nāŒ Failed to start edge application:', error.message);
613
+ console.error("\nāŒ Failed to start edge application:", error.message);
591
614
  // Provide more specific guidance for common errors
592
- if (error.message?.includes('Failed to connect any repositories')) {
593
- console.error('\nšŸ’” This usually happens when:');
594
- console.error(' - All Linear OAuth tokens have expired');
595
- console.error(' - The Linear API is temporarily unavailable');
596
- console.error(' - Your network connection is having issues');
597
- console.error('\nPlease check your edge configuration and try again.');
615
+ if (error.message?.includes("Failed to connect any repositories")) {
616
+ console.error("\nšŸ’” This usually happens when:");
617
+ console.error(" - All Linear OAuth tokens have expired");
618
+ console.error(" - The Linear API is temporarily unavailable");
619
+ console.error(" - Your network connection is having issues");
620
+ console.error("\nPlease check your edge configuration and try again.");
598
621
  }
599
622
  await this.shutdown();
600
623
  process.exit(1);
601
624
  }
602
625
  }
626
+ /**
627
+ * Check if a branch exists locally or remotely
628
+ */
629
+ async branchExists(branchName, repoPath) {
630
+ const { execSync } = await import("node:child_process");
631
+ try {
632
+ // Check if branch exists locally
633
+ execSync(`git rev-parse --verify "${branchName}"`, {
634
+ cwd: repoPath,
635
+ stdio: "pipe",
636
+ });
637
+ return true;
638
+ }
639
+ catch {
640
+ // Branch doesn't exist locally, check remote
641
+ try {
642
+ execSync(`git ls-remote --heads origin "${branchName}"`, {
643
+ cwd: repoPath,
644
+ stdio: "pipe",
645
+ });
646
+ return true;
647
+ }
648
+ catch {
649
+ return false;
650
+ }
651
+ }
652
+ }
603
653
  /**
604
654
  * Set up event handlers for EdgeWorker
605
655
  */
@@ -607,69 +657,70 @@ class EdgeApp {
607
657
  if (!this.edgeWorker)
608
658
  return;
609
659
  // Session events
610
- this.edgeWorker.on('session:started', (issueId, _issue, repositoryId) => {
660
+ this.edgeWorker.on("session:started", (issueId, _issue, repositoryId) => {
611
661
  console.log(`Started session for issue ${issueId} in repository ${repositoryId}`);
612
662
  });
613
- this.edgeWorker.on('session:ended', (issueId, exitCode, repositoryId) => {
663
+ this.edgeWorker.on("session:ended", (issueId, exitCode, repositoryId) => {
614
664
  console.log(`Session for issue ${issueId} ended with exit code ${exitCode} in repository ${repositoryId}`);
615
665
  });
616
666
  // Connection events
617
- this.edgeWorker.on('connected', (token) => {
667
+ this.edgeWorker.on("connected", (token) => {
618
668
  console.log(`āœ… Connected to proxy with token ending in ...${token.slice(-4)}`);
619
669
  });
620
- this.edgeWorker.on('disconnected', (token, reason) => {
621
- console.error(`āŒ Disconnected from proxy (token ...${token.slice(-4)}): ${reason || 'Unknown reason'}`);
670
+ this.edgeWorker.on("disconnected", (token, reason) => {
671
+ console.error(`āŒ Disconnected from proxy (token ...${token.slice(-4)}): ${reason || "Unknown reason"}`);
622
672
  });
623
673
  // Error events
624
- this.edgeWorker.on('error', (error) => {
625
- console.error('EdgeWorker error:', error);
674
+ this.edgeWorker.on("error", (error) => {
675
+ console.error("EdgeWorker error:", error);
626
676
  });
627
677
  }
628
678
  /**
629
679
  * Create a git worktree for an issue
630
680
  */
631
681
  async createGitWorktree(issue, repository) {
632
- const { execSync } = await import('child_process');
633
- const { existsSync } = await import('fs');
634
- const { join } = await import('path');
682
+ const { execSync } = await import("node:child_process");
683
+ const { existsSync } = await import("node:fs");
684
+ const { join } = await import("node:path");
635
685
  try {
636
686
  // Verify this is a git repository
637
687
  try {
638
- execSync('git rev-parse --git-dir', {
688
+ execSync("git rev-parse --git-dir", {
639
689
  cwd: repository.repositoryPath,
640
- stdio: 'pipe'
690
+ stdio: "pipe",
641
691
  });
642
692
  }
643
- catch (e) {
693
+ catch (_e) {
644
694
  console.error(`${repository.repositoryPath} is not a git repository`);
645
- throw new Error('Not a git repository');
695
+ throw new Error("Not a git repository");
646
696
  }
647
697
  // Sanitize branch name by removing backticks to prevent command injection
648
- const sanitizeBranchName = (name) => name ? name.replace(/`/g, '') : name;
698
+ const sanitizeBranchName = (name) => name ? name.replace(/`/g, "") : name;
649
699
  // Use Linear's preferred branch name, or generate one if not available
650
- const rawBranchName = issue.branchName || `${issue.identifier}-${issue.title?.toLowerCase().replace(/\s+/g, '-').substring(0, 30)}`;
700
+ const rawBranchName = issue.branchName ||
701
+ `${issue.identifier}-${issue.title?.toLowerCase().replace(/\s+/g, "-").substring(0, 30)}`;
651
702
  const branchName = sanitizeBranchName(rawBranchName);
652
703
  const workspacePath = join(repository.workspaceBaseDir, issue.identifier);
653
704
  // Ensure workspace directory exists
654
705
  execSync(`mkdir -p "${repository.workspaceBaseDir}"`, {
655
706
  cwd: repository.repositoryPath,
656
- stdio: 'pipe'
707
+ stdio: "pipe",
657
708
  });
658
709
  // Check if worktree already exists
659
710
  try {
660
- const worktrees = execSync('git worktree list --porcelain', {
711
+ const worktrees = execSync("git worktree list --porcelain", {
661
712
  cwd: repository.repositoryPath,
662
- encoding: 'utf-8'
713
+ encoding: "utf-8",
663
714
  });
664
715
  if (worktrees.includes(workspacePath)) {
665
716
  console.log(`Worktree already exists at ${workspacePath}, using existing`);
666
717
  return {
667
718
  path: workspacePath,
668
- isGitWorktree: true
719
+ isGitWorktree: true,
669
720
  };
670
721
  }
671
722
  }
672
- catch (e) {
723
+ catch (_e) {
673
724
  // git worktree command failed, continue with creation
674
725
  }
675
726
  // Check if branch already exists
@@ -677,38 +728,65 @@ class EdgeApp {
677
728
  try {
678
729
  execSync(`git rev-parse --verify "${branchName}"`, {
679
730
  cwd: repository.repositoryPath,
680
- stdio: 'pipe'
731
+ stdio: "pipe",
681
732
  });
682
733
  createBranch = false;
683
734
  }
684
- catch (e) {
735
+ catch (_e) {
685
736
  // Branch doesn't exist, we'll create it
686
737
  }
738
+ // Determine base branch for this issue
739
+ let baseBranch = repository.baseBranch;
740
+ // Check if issue has a parent
741
+ try {
742
+ const parent = await issue.parent;
743
+ if (parent) {
744
+ console.log(`Issue ${issue.identifier} has parent: ${parent.identifier}`);
745
+ // Get parent's branch name
746
+ const parentRawBranchName = parent.branchName ||
747
+ `${parent.identifier}-${parent.title?.toLowerCase().replace(/\s+/g, "-").substring(0, 30)}`;
748
+ const parentBranchName = sanitizeBranchName(parentRawBranchName);
749
+ // Check if parent branch exists
750
+ const parentBranchExists = await this.branchExists(parentBranchName, repository.repositoryPath);
751
+ if (parentBranchExists) {
752
+ baseBranch = parentBranchName;
753
+ console.log(`Using parent issue branch '${parentBranchName}' as base for sub-issue ${issue.identifier}`);
754
+ }
755
+ else {
756
+ console.log(`Parent branch '${parentBranchName}' not found, using default base branch '${repository.baseBranch}'`);
757
+ }
758
+ }
759
+ }
760
+ catch (_error) {
761
+ // Parent field might not exist or couldn't be fetched, use default base branch
762
+ console.log(`No parent issue found for ${issue.identifier}, using default base branch '${repository.baseBranch}'`);
763
+ }
687
764
  // Fetch latest changes from remote
688
- console.log('Fetching latest changes from remote...');
765
+ console.log("Fetching latest changes from remote...");
689
766
  let hasRemote = true;
690
767
  try {
691
- execSync('git fetch origin', {
768
+ execSync("git fetch origin", {
692
769
  cwd: repository.repositoryPath,
693
- stdio: 'pipe'
770
+ stdio: "pipe",
694
771
  });
695
772
  }
696
773
  catch (e) {
697
- console.warn('Warning: git fetch failed, proceeding with local branch:', e.message);
774
+ console.warn("Warning: git fetch failed, proceeding with local branch:", e.message);
698
775
  hasRemote = false;
699
776
  }
700
- // Create the worktree - use remote branch if available, otherwise local
777
+ // Create the worktree - use determined base branch
701
778
  let worktreeCmd;
702
779
  if (createBranch) {
703
780
  if (hasRemote) {
704
- const remoteBranch = `origin/${repository.baseBranch}`;
781
+ // Always prefer remote version if available
782
+ const remoteBranch = `origin/${baseBranch}`;
705
783
  console.log(`Creating git worktree at ${workspacePath} from ${remoteBranch}`);
706
784
  worktreeCmd = `git worktree add "${workspacePath}" -b "${branchName}" "${remoteBranch}"`;
707
785
  }
708
786
  else {
709
- // No remote, use local base branch
710
- console.log(`Creating git worktree at ${workspacePath} from local ${repository.baseBranch}`);
711
- worktreeCmd = `git worktree add "${workspacePath}" -b "${branchName}" "${repository.baseBranch}"`;
787
+ // No remote, use local branch
788
+ console.log(`Creating git worktree at ${workspacePath} from local ${baseBranch}`);
789
+ worktreeCmd = `git worktree add "${workspacePath}" -b "${branchName}" "${baseBranch}"`;
712
790
  }
713
791
  }
714
792
  else {
@@ -718,42 +796,42 @@ class EdgeApp {
718
796
  }
719
797
  execSync(worktreeCmd, {
720
798
  cwd: repository.repositoryPath,
721
- stdio: 'pipe'
799
+ stdio: "pipe",
722
800
  });
723
801
  // Check for cyrus-setup.sh script in the repository root
724
- const setupScriptPath = join(repository.repositoryPath, 'cyrus-setup.sh');
802
+ const setupScriptPath = join(repository.repositoryPath, "cyrus-setup.sh");
725
803
  if (existsSync(setupScriptPath)) {
726
- console.log('Running cyrus-setup.sh in new worktree...');
804
+ console.log("Running cyrus-setup.sh in new worktree...");
727
805
  try {
728
- execSync('bash cyrus-setup.sh', {
806
+ execSync("bash cyrus-setup.sh", {
729
807
  cwd: workspacePath,
730
- stdio: 'inherit',
808
+ stdio: "inherit",
731
809
  env: {
732
810
  ...process.env,
733
811
  LINEAR_ISSUE_ID: issue.id,
734
812
  LINEAR_ISSUE_IDENTIFIER: issue.identifier,
735
- LINEAR_ISSUE_TITLE: issue.title || ''
736
- }
813
+ LINEAR_ISSUE_TITLE: issue.title || "",
814
+ },
737
815
  });
738
816
  }
739
817
  catch (error) {
740
- console.warn('Warning: cyrus-setup.sh failed:', error.message);
818
+ console.warn("Warning: cyrus-setup.sh failed:", error.message);
741
819
  // Continue despite setup script failure
742
820
  }
743
821
  }
744
822
  return {
745
823
  path: workspacePath,
746
- isGitWorktree: true
824
+ isGitWorktree: true,
747
825
  };
748
826
  }
749
827
  catch (error) {
750
- console.error('Failed to create git worktree:', error.message);
828
+ console.error("Failed to create git worktree:", error.message);
751
829
  // Fall back to regular directory if git worktree fails
752
830
  const fallbackPath = join(repository.workspaceBaseDir, issue.identifier);
753
- execSync(`mkdir -p "${fallbackPath}"`, { stdio: 'pipe' });
831
+ execSync(`mkdir -p "${fallbackPath}"`, { stdio: "pipe" });
754
832
  return {
755
833
  path: fallbackPath,
756
- isGitWorktree: false
834
+ isGitWorktree: false,
757
835
  };
758
836
  }
759
837
  }
@@ -764,31 +842,34 @@ class EdgeApp {
764
842
  if (this.isShuttingDown)
765
843
  return;
766
844
  this.isShuttingDown = true;
767
- console.log('\nShutting down edge worker...');
845
+ console.log("\nShutting down edge worker...");
768
846
  // Stop edge worker (includes stopping shared application server)
769
847
  if (this.edgeWorker) {
770
848
  await this.edgeWorker.stop();
771
849
  }
772
- console.log('Shutdown complete');
850
+ console.log("Shutdown complete");
773
851
  process.exit(0);
774
852
  }
775
853
  }
776
854
  // Helper function to check Linear token status
777
855
  async function checkLinearToken(token) {
778
856
  try {
779
- const response = await fetch('https://api.linear.app/graphql', {
780
- method: 'POST',
857
+ const response = await fetch("https://api.linear.app/graphql", {
858
+ method: "POST",
781
859
  headers: {
782
- 'Content-Type': 'application/json',
783
- 'Authorization': token
860
+ "Content-Type": "application/json",
861
+ Authorization: token,
784
862
  },
785
863
  body: JSON.stringify({
786
- query: '{ viewer { id email name } }'
787
- })
864
+ query: "{ viewer { id email name } }",
865
+ }),
788
866
  });
789
- const data = await response.json();
867
+ const data = (await response.json());
790
868
  if (data.errors) {
791
- return { valid: false, error: data.errors[0]?.message || 'Unknown error' };
869
+ return {
870
+ valid: false,
871
+ error: data.errors[0]?.message || "Unknown error",
872
+ };
792
873
  }
793
874
  return { valid: true };
794
875
  }
@@ -801,16 +882,16 @@ async function checkTokensCommand() {
801
882
  const app = new EdgeApp();
802
883
  const configPath = app.getEdgeConfigPath();
803
884
  if (!existsSync(configPath)) {
804
- console.error('No edge configuration found. Please run setup first.');
885
+ console.error("No edge configuration found. Please run setup first.");
805
886
  process.exit(1);
806
887
  }
807
- const config = JSON.parse(readFileSync(configPath, 'utf-8'));
808
- console.log('Checking Linear tokens...\n');
888
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
889
+ console.log("Checking Linear tokens...\n");
809
890
  for (const repo of config.repositories) {
810
891
  process.stdout.write(`${repo.name} (${repo.linearWorkspaceName}): `);
811
892
  const result = await checkLinearToken(repo.linearToken);
812
893
  if (result.valid) {
813
- console.log('āœ… Valid');
894
+ console.log("āœ… Valid");
814
895
  }
815
896
  else {
816
897
  console.log(`āŒ Invalid - ${result.error}`);
@@ -822,34 +903,34 @@ async function refreshTokenCommand() {
822
903
  const app = new EdgeApp();
823
904
  const configPath = app.getEdgeConfigPath();
824
905
  if (!existsSync(configPath)) {
825
- console.error('No edge configuration found. Please run setup first.');
906
+ console.error("No edge configuration found. Please run setup first.");
826
907
  process.exit(1);
827
908
  }
828
- const config = JSON.parse(readFileSync(configPath, 'utf-8'));
909
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
829
910
  // Show repositories with their token status
830
- console.log('Checking current token status...\n');
911
+ console.log("Checking current token status...\n");
831
912
  const tokenStatuses = [];
832
913
  for (const repo of config.repositories) {
833
914
  const result = await checkLinearToken(repo.linearToken);
834
915
  tokenStatuses.push({ repo, valid: result.valid });
835
- console.log(`${tokenStatuses.length}. ${repo.name} (${repo.linearWorkspaceName}): ${result.valid ? 'āœ… Valid' : 'āŒ Invalid'}`);
916
+ console.log(`${tokenStatuses.length}. ${repo.name} (${repo.linearWorkspaceName}): ${result.valid ? "āœ… Valid" : "āŒ Invalid"}`);
836
917
  }
837
918
  // Ask which token to refresh
838
919
  const rl = readline.createInterface({
839
920
  input: process.stdin,
840
- output: process.stdout
921
+ output: process.stdout,
841
922
  });
842
- const answer = await new Promise(resolve => {
923
+ const answer = await new Promise((resolve) => {
843
924
  rl.question('\nWhich repository token would you like to refresh? (Enter number or "all"): ', resolve);
844
925
  });
845
926
  const indicesToRefresh = [];
846
- if (answer.toLowerCase() === 'all') {
927
+ if (answer.toLowerCase() === "all") {
847
928
  indicesToRefresh.push(...Array.from({ length: tokenStatuses.length }, (_, i) => i));
848
929
  }
849
930
  else {
850
931
  const index = parseInt(answer) - 1;
851
- if (isNaN(index) || index < 0 || index >= tokenStatuses.length) {
852
- console.error('Invalid selection');
932
+ if (Number.isNaN(index) || index < 0 || index >= tokenStatuses.length) {
933
+ console.error("Invalid selection");
853
934
  rl.close();
854
935
  process.exit(1);
855
936
  }
@@ -862,9 +943,11 @@ async function refreshTokenCommand() {
862
943
  continue;
863
944
  const { repo } = tokenStatus;
864
945
  console.log(`\nRefreshing token for ${repo.name} (${repo.linearWorkspaceName || repo.linearWorkspaceId})...`);
865
- console.log('Opening Linear OAuth flow in your browser...');
946
+ console.log("Opening Linear OAuth flow in your browser...");
866
947
  // Use the proxy's OAuth flow with a callback to localhost
867
- const serverPort = process.env.CYRUS_SERVER_PORT ? parseInt(process.env.CYRUS_SERVER_PORT, 10) : 3456;
948
+ const serverPort = process.env.CYRUS_SERVER_PORT
949
+ ? parseInt(process.env.CYRUS_SERVER_PORT, 10)
950
+ : 3456;
868
951
  const callbackUrl = `http://localhost:${serverPort}/callback`;
869
952
  const oauthUrl = `https://cyrus-proxy.ceedar.workers.dev/oauth/authorize?callback=${encodeURIComponent(callbackUrl)}`;
870
953
  console.log(`\nPlease complete the OAuth flow in your browser.`);
@@ -873,10 +956,10 @@ async function refreshTokenCommand() {
873
956
  let tokenReceived = null;
874
957
  const server = await new Promise((resolve) => {
875
958
  const s = http.createServer((req, res) => {
876
- if (req.url?.startsWith('/callback')) {
959
+ if (req.url?.startsWith("/callback")) {
877
960
  const url = new URL(req.url, `http://localhost:${serverPort}`);
878
- tokenReceived = url.searchParams.get('token');
879
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
961
+ tokenReceived = url.searchParams.get("token");
962
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
880
963
  res.end(`
881
964
  <html>
882
965
  <head>
@@ -892,11 +975,11 @@ async function refreshTokenCommand() {
892
975
  }
893
976
  else {
894
977
  res.writeHead(404);
895
- res.end('Not found');
978
+ res.end("Not found");
896
979
  }
897
980
  });
898
981
  s.listen(serverPort, () => {
899
- console.log('Waiting for OAuth callback...');
982
+ console.log("Waiting for OAuth callback...");
900
983
  resolve(s);
901
984
  });
902
985
  });
@@ -904,12 +987,12 @@ async function refreshTokenCommand() {
904
987
  // Wait for the token with timeout
905
988
  const startTime = Date.now();
906
989
  while (!tokenReceived && Date.now() - startTime < 120000) {
907
- await new Promise(resolve => setTimeout(resolve, 100));
990
+ await new Promise((resolve) => setTimeout(resolve, 100));
908
991
  }
909
992
  server.close();
910
993
  const newToken = tokenReceived;
911
- if (!newToken || !newToken.startsWith('lin_oauth_')) {
912
- console.error('Invalid token received from OAuth flow');
994
+ if (!newToken || !newToken.startsWith("lin_oauth_")) {
995
+ console.error("Invalid token received from OAuth flow");
913
996
  continue;
914
997
  }
915
998
  // Verify the new token
@@ -935,33 +1018,33 @@ async function refreshTokenCommand() {
935
1018
  }
936
1019
  // Save the updated config
937
1020
  writeFileSync(configPath, JSON.stringify(config, null, 2));
938
- console.log('\nāœ… Configuration saved');
1021
+ console.log("\nāœ… Configuration saved");
939
1022
  rl.close();
940
1023
  }
941
1024
  // Parse command
942
- const command = args[0] || 'start';
1025
+ const command = args[0] || "start";
943
1026
  // Execute appropriate command
944
1027
  switch (command) {
945
- case 'check-tokens':
946
- checkTokensCommand().catch(error => {
947
- console.error('Error:', error);
1028
+ case "check-tokens":
1029
+ checkTokensCommand().catch((error) => {
1030
+ console.error("Error:", error);
948
1031
  process.exit(1);
949
1032
  });
950
1033
  break;
951
- case 'refresh-token':
952
- refreshTokenCommand().catch(error => {
953
- console.error('Error:', error);
1034
+ case "refresh-token":
1035
+ refreshTokenCommand().catch((error) => {
1036
+ console.error("Error:", error);
954
1037
  process.exit(1);
955
1038
  });
956
1039
  break;
957
- case 'start':
958
- default:
1040
+ default: {
959
1041
  // Create and start the app
960
1042
  const app = new EdgeApp();
961
- app.start().catch(error => {
962
- console.error('Fatal error:', error);
1043
+ app.start().catch((error) => {
1044
+ console.error("Fatal error:", error);
963
1045
  process.exit(1);
964
1046
  });
965
1047
  break;
1048
+ }
966
1049
  }
967
1050
  //# sourceMappingURL=app.js.map