flightdesk 0.1.5 → 0.1.6

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 (3) hide show
  1. package/main.js +199 -138
  2. package/main.js.map +3 -3
  3. package/package.json +1 -1
package/main.js CHANGED
@@ -3249,10 +3249,16 @@ async function checkAuth() {
3249
3249
  const { context } = await launchBrowser(true);
3250
3250
  try {
3251
3251
  const page = await context.newPage();
3252
- await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded" });
3252
+ page.setDefaultTimeout(3e4);
3253
+ await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded", timeout: 3e4 });
3254
+ await page.waitForTimeout(3e3);
3253
3255
  const url = page.url();
3254
- const isLoggedIn = !url.includes("/login") && !url.includes("/oauth");
3256
+ console.log(" Final URL:", url);
3257
+ const isLoggedIn = !url.includes("/login") && !url.includes("/oauth") && !url.includes("accounts.google") && url.includes("claude.ai");
3255
3258
  return isLoggedIn;
3259
+ } catch (error) {
3260
+ console.error("Auth check failed:", error.message);
3261
+ return false;
3256
3262
  } finally {
3257
3263
  await context.close();
3258
3264
  }
@@ -3262,15 +3268,20 @@ async function openForLogin() {
3262
3268
  throw new Error("Playwright not installed");
3263
3269
  }
3264
3270
  console.log("Opening browser for Claude login...");
3265
- console.log("Please log in, then close the browser window.");
3266
3271
  const { context } = await launchBrowser(false);
3267
3272
  try {
3268
3273
  const page = await context.newPage();
3269
- await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded" });
3270
- await new Promise((resolve) => {
3271
- context.on("close", () => resolve());
3272
- });
3273
- console.log("Browser closed. Checking login status...");
3274
+ await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded", timeout: 6e4 });
3275
+ console.log("\n\u{1F449} Press ENTER here when you have logged in...\n");
3276
+ await waitForEnter();
3277
+ console.log("Verifying login in current session...");
3278
+ await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded", timeout: 3e4 });
3279
+ await page.waitForTimeout(2e3);
3280
+ const url = page.url();
3281
+ console.log(" Final URL:", url);
3282
+ const isLoggedIn = !url.includes("/login") && !url.includes("/oauth") && !url.includes("accounts.google") && url.includes("claude.ai");
3283
+ console.log("Closing browser...");
3284
+ return isLoggedIn;
3274
3285
  } finally {
3275
3286
  try {
3276
3287
  await context.close();
@@ -3278,6 +3289,19 @@ async function openForLogin() {
3278
3289
  }
3279
3290
  }
3280
3291
  }
3292
+ function waitForEnter() {
3293
+ return new Promise((resolve) => {
3294
+ const readline3 = require("readline");
3295
+ const rl = readline3.createInterface({
3296
+ input: process.stdin,
3297
+ output: process.stdout
3298
+ });
3299
+ rl.question("", () => {
3300
+ rl.close();
3301
+ resolve();
3302
+ });
3303
+ });
3304
+ }
3281
3305
  async function monitorSession(sessionUrl, options = {}) {
3282
3306
  if (!await isPlaywrightAvailable()) {
3283
3307
  return { status: "error", error: "Playwright not installed" };
@@ -3305,7 +3329,7 @@ async function monitorSession(sessionUrl, options = {}) {
3305
3329
  if (branchName) {
3306
3330
  result.branchName = branchName;
3307
3331
  }
3308
- const prButton = await page.$('button:has-text("Create PR"), button:has-text("Create Pull Request"), button:has-text("Open PR")');
3332
+ const prButton = await page.$('button:has-text("Create PR"):not([aria-haspopup])');
3309
3333
  result.hasPrButton = !!prButton;
3310
3334
  if (autoPr && prButton) {
3311
3335
  console.log(' Clicking "Create PR" button...');
@@ -3327,21 +3351,18 @@ async function monitorSession(sessionUrl, options = {}) {
3327
3351
  }
3328
3352
  }
3329
3353
  async function extractBranchName(page) {
3330
- const selectors = [
3331
- // Look for git branch indicators
3332
- '[data-testid="branch-name"]',
3333
- ".branch-name",
3334
- // Look for text patterns
3335
- "text=/\\b(feature|fix|bugfix|hotfix|release|develop|main|master)\\/[\\w-]+/i"
3354
+ const primarySelectors = [
3355
+ "button.group\\/copy span.truncate"
3356
+ // New branch name (verified)
3336
3357
  ];
3337
- for (const selector of selectors) {
3358
+ for (const selector of primarySelectors) {
3338
3359
  try {
3339
3360
  const element = await page.$(selector);
3340
3361
  if (element) {
3341
3362
  const text = await element.textContent();
3342
3363
  if (text) {
3343
3364
  const cleaned = text.trim();
3344
- if (cleaned && !cleaned.includes(" ")) {
3365
+ if (cleaned && cleaned.length > 2 && !cleaned.includes(" ")) {
3345
3366
  return cleaned;
3346
3367
  }
3347
3368
  }
@@ -3349,24 +3370,6 @@ async function extractBranchName(page) {
3349
3370
  } catch {
3350
3371
  }
3351
3372
  }
3352
- try {
3353
- const pageContent = await page.content();
3354
- const branchPatterns = [
3355
- /(?:branch|checkout|switched to)[:\s]+['"]?([a-zA-Z0-9/_-]+)['"]?/i,
3356
- /(?:On branch|HEAD -> )\s*([a-zA-Z0-9/_-]+)/i,
3357
- /\[([a-zA-Z0-9/_-]+)\]/
3358
- ];
3359
- for (const pattern of branchPatterns) {
3360
- const match = pageContent.match(pattern);
3361
- if (match && match[1]) {
3362
- const branch = match[1].trim();
3363
- if (branch.length > 2 && branch.length < 100 && !branch.includes("<")) {
3364
- return branch;
3365
- }
3366
- }
3367
- }
3368
- } catch {
3369
- }
3370
3373
  return null;
3371
3374
  }
3372
3375
  async function extractPrUrl(page) {
@@ -3424,15 +3427,13 @@ async function authCommand() {
3424
3427
  }
3425
3428
  console.log("\u274C Not logged in to Claude.\n");
3426
3429
  console.log("Opening browser for login...");
3427
- console.log("Please log in to your Claude account, then close the browser.\n");
3428
- await openForLogin();
3429
- console.log("\nVerifying login...");
3430
- const nowAuthenticated = await checkAuth();
3431
- if (nowAuthenticated) {
3432
- console.log("\u2705 Successfully logged in!");
3433
- console.log("\nThe watch daemon can now monitor your Claude Code sessions.");
3430
+ console.log("Please log in to your Claude account.\n");
3431
+ const loginSuccessful = await openForLogin();
3432
+ if (loginSuccessful) {
3433
+ console.log("\n\u2705 Successfully logged in!");
3434
+ console.log("The watch daemon can now monitor your Claude Code sessions.");
3434
3435
  } else {
3435
- console.log("\u274C Login was not detected.");
3436
+ console.log("\n\u274C Login was not detected.");
3436
3437
  console.log("Please try again with: flightdesk auth");
3437
3438
  }
3438
3439
  }
@@ -3442,6 +3443,7 @@ var FlightDeskAPI = class {
3442
3443
  constructor(org2) {
3443
3444
  this.apiUrl = org2.apiUrl;
3444
3445
  this.apiKey = org2.apiKey;
3446
+ this.organizationId = org2.id;
3445
3447
  }
3446
3448
  async graphql(query, variables) {
3447
3449
  const response = await fetch(`${this.apiUrl}/graphql`, {
@@ -3566,15 +3568,15 @@ var FlightDeskAPI = class {
3566
3568
  // ============================================================================
3567
3569
  async listProjects() {
3568
3570
  const query = `
3569
- query ListProjects {
3570
- userProjects {
3571
+ query ListProjects($organizationId: String!) {
3572
+ userProjects(organizationId: $organizationId) {
3571
3573
  id
3572
3574
  name
3573
3575
  githubRepo
3574
3576
  }
3575
3577
  }
3576
3578
  `;
3577
- const result = await this.graphql(query);
3579
+ const result = await this.graphql(query, { organizationId: this.organizationId });
3578
3580
  return result.userProjects;
3579
3581
  }
3580
3582
  async getProject(projectId) {
@@ -3902,7 +3904,7 @@ Watching... (Ctrl+C to stop)
3902
3904
  clearInterval(intervalId);
3903
3905
  process.exit(0);
3904
3906
  });
3905
- await new Promise(() => {
3907
+ await new Promise((_resolve) => {
3906
3908
  });
3907
3909
  }
3908
3910
  async function processSessionInfo(api, task2, info) {
@@ -4276,7 +4278,7 @@ async function orgSetDefaultCommand(orgId) {
4276
4278
  var import_child_process = require("child_process");
4277
4279
  async function contextCommand() {
4278
4280
  console.log("\n\u{1F50D} Current Context\n");
4279
- let repoInfo = {};
4281
+ const repoInfo = {};
4280
4282
  try {
4281
4283
  const remoteUrl = (0, import_child_process.execSync)("git remote get-url origin", {
4282
4284
  encoding: "utf-8",
@@ -4387,52 +4389,60 @@ async function syncCommand() {
4387
4389
  // apps/cli/src/lib/claude-selectors.ts
4388
4390
  var ClaudeSelectors = {
4389
4391
  // ============================================================================
4390
- // Sidebar - Session List
4392
+ // Sidebar - Session List (Verified 2026-02-23)
4391
4393
  // ============================================================================
4392
4394
  sidebar: {
4393
4395
  /** New task input (textarea in sidebar) */
4394
- newTaskInput: 'aside textarea[placeholder*="task"]',
4396
+ newTaskInput: 'textarea[placeholder="Ask Claude to write code..."]',
4395
4397
  /** Session list container */
4396
- sessionList: "aside nav",
4398
+ sessionList: ".flex.flex-col.gap-0\\.5.px-1",
4397
4399
  /** Individual session items - use :has-text() with title */
4398
- sessionItem: (title) => `aside a:has-text("${title}")`,
4399
- /** All session links */
4400
- allSessions: 'aside nav a[href*="/chat/"]',
4401
- /** Active/selected session */
4402
- activeSession: 'aside a[aria-current="page"]',
4400
+ sessionItem: (title) => `.cursor-pointer:has-text("${title}")`,
4401
+ /** All session items (div elements with cursor-pointer class) */
4402
+ allSessions: ".flex.flex-col.gap-0\\.5.px-1 .cursor-pointer",
4403
+ /** Session by index (0-indexed) */
4404
+ sessionByIndex: (index) => `.flex.flex-col.gap-0\\.5.px-1 > div:nth-child(${index + 1}) .cursor-pointer`,
4405
+ /** Active/selected session (has bg-bg-300 class) */
4406
+ activeSession: ".cursor-pointer.bg-bg-300",
4403
4407
  /** Session title within a session item */
4404
4408
  sessionTitle: "button.text-sm.font-medium.truncate",
4405
- /** Session menu button (three dots) */
4406
- sessionMenuButton: 'button[aria-label*="menu"], button:has(svg[class*="dots"])'
4409
+ /** Session archive button (icon-only, no aria-label) */
4410
+ sessionArchiveButton: (title) => `.cursor-pointer:has-text("${title}") button`,
4411
+ /** Search sessions button */
4412
+ searchButton: 'button[aria-label="Search \u2318K"]',
4413
+ /** User profile button */
4414
+ userProfileButton: '[data-testid="code-user-menu-button"]'
4407
4415
  },
4408
4416
  // ============================================================================
4409
- // Branch Bar (top of chat when branch exists)
4417
+ // Branch Bar (between chat messages and chat input) - Verified 2026-02-23
4410
4418
  // ============================================================================
4411
4419
  branchBar: {
4412
- /** Container for branch info */
4413
- container: 'div:has(button:has-text("Branch"))',
4420
+ /** Container for branch bar */
4421
+ container: ".flex.items-center.gap-2.w-full.p-2",
4414
4422
  /** Branch name display - the copyable span */
4415
4423
  branchName: "button.group\\/copy span.truncate",
4416
- /** Branch selector dropdown trigger */
4417
- branchSelector: 'button[aria-haspopup="listbox"]:has-text("Branch")',
4418
- /** Copy branch button */
4419
- copyBranchButton: "button.group\\/copy"
4424
+ /** Base/target branch dropdown (e.g., "develop") */
4425
+ baseBranchSelector: '.flex.items-center.gap-2.w-full.p-2 button[aria-haspopup="menu"]',
4426
+ /** Copy branch button (click to copy branch name) */
4427
+ copyBranchButton: "button.group\\/copy",
4428
+ /** Diff stats button (+N -M) */
4429
+ diffStatsButton: ".flex.items-center.gap-2.w-full.p-2 button.border-0\\.5"
4420
4430
  },
4421
4431
  // ============================================================================
4422
- // Pull Request Controls
4432
+ // Pull Request Controls - Verified 2026-02-23
4423
4433
  // ============================================================================
4424
4434
  pullRequest: {
4425
4435
  /** Create PR button (left half - immediate action) - CAREFUL: clicks immediately! */
4426
4436
  createPrButton: 'button:has-text("Create PR"):not([aria-haspopup])',
4427
- /** PR dropdown trigger (right half - shows options) */
4428
- prDropdown: 'button[aria-haspopup="menu"]:has(svg[class*="chevron"])',
4437
+ /** PR dropdown trigger (right half - shows options) - SAFE to click */
4438
+ prDropdown: 'button.rounded-r-md[aria-haspopup="menu"]',
4429
4439
  /** PR status indicator when PR exists */
4430
4440
  prStatus: 'a[href*="/pull/"], a[href*="/merge_requests/"]',
4431
4441
  /** View PR link */
4432
4442
  viewPrLink: 'a:has-text("View PR"), a:has-text("View Pull Request")'
4433
4443
  },
4434
4444
  // ============================================================================
4435
- // Chat Area
4445
+ // Chat Area - Verified 2026-02-23
4436
4446
  // ============================================================================
4437
4447
  chat: {
4438
4448
  /** Main chat container */
@@ -4443,43 +4453,56 @@ var ClaudeSelectors = {
4443
4453
  userMessages: 'div[data-testid="user-message"]',
4444
4454
  /** Assistant messages */
4445
4455
  assistantMessages: 'div[data-testid="assistant-message"]',
4446
- /** Chat input (ProseMirror contenteditable) */
4447
- input: 'div.ProseMirror[contenteditable="true"]',
4456
+ /** Chat input (ProseMirror contenteditable) - NOT a textarea */
4457
+ input: '[aria-label="Enter your turn"]',
4458
+ /** Chat input fallback */
4459
+ inputFallback: 'div.tiptap.ProseMirror[contenteditable="true"]',
4460
+ /** Toggle menu button (+) */
4461
+ toggleMenuButton: 'button[aria-label="Toggle menu"]',
4448
4462
  /** Send button */
4449
- sendButton: 'button[aria-label*="Send"], button:has(svg[class*="arrow"])',
4463
+ sendButton: 'form.w-full button[aria-label="Submit"]',
4450
4464
  /** Stop button (during generation) */
4451
4465
  stopButton: 'button:has-text("Stop")'
4452
4466
  },
4453
4467
  // ============================================================================
4454
- // Model Selector
4468
+ // Model Selector - Verified 2026-02-23
4455
4469
  // ============================================================================
4456
4470
  model: {
4457
- /** Model selector dropdown */
4458
- selector: 'button[aria-haspopup="listbox"]:has-text("Claude")',
4459
- /** Model options in dropdown */
4460
- options: 'div[role="listbox"] div[role="option"]',
4471
+ /** Model selector dropdown (sidebar) */
4472
+ sidebarSelector: 'form:not(.w-full) [data-testid="model-selector-dropdown"]',
4473
+ /** Model selector dropdown (main chat) */
4474
+ chatSelector: 'form.w-full [data-testid="model-selector-dropdown"]',
4475
+ /** Model options in dropdown (role="menuitem") */
4476
+ options: '[role="menuitem"]',
4461
4477
  /** Specific model option */
4462
- option: (modelName) => `div[role="option"]:has-text("${modelName}")`
4478
+ option: (modelName) => `[role="menuitem"]:has-text("${modelName}")`,
4479
+ /** Quick model selectors */
4480
+ opus: '[role="menuitem"]:has-text("Opus")',
4481
+ sonnet: '[role="menuitem"]:has-text("Sonnet")',
4482
+ haiku: '[role="menuitem"]:has-text("Haiku")'
4463
4483
  },
4464
4484
  // ============================================================================
4465
- // Mode Toggle (Chat/Code)
4485
+ // Mode Toggle - Verified 2026-02-23
4486
+ // Text alternates between "Auto accept edits" and "Plan mode"
4466
4487
  // ============================================================================
4467
4488
  mode: {
4468
- /** Mode toggle button - simple button, not dropdown */
4469
- toggle: 'button:has-text("Code"), button:has-text("Chat")',
4470
- /** Current mode indicator */
4471
- currentMode: 'button[aria-pressed="true"]'
4489
+ /** Mode toggle button in sidebar - simple toggle, not dropdown */
4490
+ sidebarToggle: 'form:not(.w-full) button:has-text("Auto accept edits"), form:not(.w-full) button:has-text("Plan mode")',
4491
+ /** Mode toggle button in main chat */
4492
+ chatToggle: 'form.w-full button:has-text("Auto accept edits"), form.w-full button:has-text("Plan mode")'
4472
4493
  },
4473
4494
  // ============================================================================
4474
- // Repository Connection
4495
+ // Repository Connection - Verified 2026-02-23
4475
4496
  // ============================================================================
4476
4497
  repository: {
4477
- /** Connect repo button (when no repo connected) */
4478
- connectButton: 'button:has-text("Connect"), button:has-text("Repository")',
4479
- /** Repo name when connected */
4480
- repoName: 'a[href*="github.com"], span:has-text("/")',
4481
- /** Disconnect/change repo button */
4482
- changeRepoButton: 'button:has-text("Change"), button:has-text("Disconnect")'
4498
+ /** Repo button in sidebar (shows current repo name) */
4499
+ repoButton: "form:not(.w-full) button.flex.items-center.gap-1.min-w-0.rounded",
4500
+ /** Repo button by name */
4501
+ repoButtonByName: (repoName) => `form:not(.w-full) button:has-text("${repoName}")`,
4502
+ /** Add repository button (hidden until hover) */
4503
+ addRepoButton: 'button[aria-label="Add repository"]',
4504
+ /** Select repository dropdown */
4505
+ selectRepoDropdown: 'form:not(.w-full) button[aria-haspopup="menu"]:has-text("Select repository")'
4483
4506
  },
4484
4507
  // ============================================================================
4485
4508
  // Session Archive
@@ -4515,17 +4538,6 @@ var ClaudeSelectors = {
4515
4538
  listboxOptions: 'div[role="option"]'
4516
4539
  }
4517
4540
  };
4518
- function parseRepoName(text) {
4519
- const slashMatch = text.match(/^([a-zA-Z0-9_-]+)\/([a-zA-Z0-9_.-]+)$/);
4520
- if (slashMatch) {
4521
- return { owner: slashMatch[1], repo: slashMatch[2] };
4522
- }
4523
- const urlMatch = text.match(/github\.com\/([a-zA-Z0-9_-]+)\/([a-zA-Z0-9_.-]+)/);
4524
- if (urlMatch) {
4525
- return { owner: urlMatch[1], repo: urlMatch[2] };
4526
- }
4527
- return null;
4528
- }
4529
4541
 
4530
4542
  // apps/cli/src/commands/import.ts
4531
4543
  var fs3 = __toESM(require("fs"));
@@ -4587,7 +4599,7 @@ Found ${sessions.length} sessions:
4587
4599
  }
4588
4600
  for (const [repoName, repoSessions] of sessionsByRepo) {
4589
4601
  const matchingProject = existingProjects.find(
4590
- (p) => p.githubRepo?.includes(repoName) || p.name === repoName
4602
+ (p) => p.githubRepo && p.githubRepo.endsWith(`/${repoName}`)
4591
4603
  );
4592
4604
  if (matchingProject) {
4593
4605
  console.log(`\u{1F4C1} ${repoName} \u2192 Project: ${matchingProject.name} (${matchingProject.id.slice(0, 8)})`);
@@ -4626,12 +4638,52 @@ Summary:`);
4626
4638
  console.log("\u{1F4A1} Run without --dry-run to create tasks in FlightDesk");
4627
4639
  return;
4628
4640
  }
4629
- console.log("To import these sessions:");
4630
- console.log("");
4631
- console.log("1. Create projects in FlightDesk for each repository");
4632
- console.log('2. Use `flightdesk register <project-id> --title "<session-title>" --view-url "<url>"` for each session');
4641
+ const importableSessions = [];
4642
+ for (const [repoName, repoSessions] of sessionsByRepo) {
4643
+ const matchingProject = existingProjects.find(
4644
+ (p) => p.githubRepo && p.githubRepo.endsWith(`/${repoName}`)
4645
+ );
4646
+ if (matchingProject) {
4647
+ for (const session of repoSessions) {
4648
+ importableSessions.push({ session, project: matchingProject });
4649
+ }
4650
+ }
4651
+ }
4652
+ if (importableSessions.length === 0) {
4653
+ console.log("No sessions can be imported (no matching projects).");
4654
+ console.log("Create projects in FlightDesk first for the repositories you want to track.");
4655
+ return;
4656
+ }
4657
+ console.log(`
4658
+ \u{1F680} Importing ${importableSessions.length} sessions...
4659
+ `);
4660
+ let created = 0;
4661
+ let failed = 0;
4662
+ for (const { session, project } of importableSessions) {
4663
+ try {
4664
+ const task2 = await api.createTask({
4665
+ projectId: project.id,
4666
+ title: session.title,
4667
+ description: `Imported from Claude Code session`
4668
+ });
4669
+ await api.updateTask(task2.id, {
4670
+ branchName: session.branchName,
4671
+ sessionViewUrl: session.url
4672
+ });
4673
+ console.log(` \u2705 ${session.title.slice(0, 50)}`);
4674
+ created++;
4675
+ } catch (error) {
4676
+ console.error(` \u274C ${session.title.slice(0, 50)}: ${error.message}`);
4677
+ failed++;
4678
+ }
4679
+ }
4633
4680
  console.log("");
4634
- console.log("Or use the web interface at your FlightDesk dashboard.");
4681
+ console.log("\u2500".repeat(60));
4682
+ console.log(`
4683
+ Import complete:`);
4684
+ console.log(` Created: ${created}`);
4685
+ console.log(` Failed: ${failed}`);
4686
+ console.log(` Skipped (no matching project): ${sessions.length - importableSessions.length}`);
4635
4687
  }
4636
4688
  async function scanClaudeSessions(options) {
4637
4689
  if (!playwright2) {
@@ -4647,36 +4699,59 @@ async function scanClaudeSessions(options) {
4647
4699
  });
4648
4700
  try {
4649
4701
  const page = await context.newPage();
4650
- page.setDefaultTimeout(3e4);
4651
- console.log("Navigating to Claude.ai...");
4652
- await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded" });
4653
- await page.waitForTimeout(3e3);
4702
+ page.setDefaultTimeout(6e4);
4703
+ console.log("Navigating to Claude Code...");
4704
+ await page.goto("https://claude.ai/code", { waitUntil: "domcontentloaded", timeout: 6e4 });
4705
+ if (options.verbose) {
4706
+ console.log("Waiting for page to load...");
4707
+ }
4708
+ await page.waitForTimeout(5e3);
4654
4709
  const url = page.url();
4710
+ if (options.verbose) {
4711
+ console.log("Current URL:", url);
4712
+ }
4655
4713
  if (url.includes("/login") || url.includes("/oauth")) {
4656
4714
  console.log("\n\u26A0\uFE0F Not logged into Claude. Run: flightdesk auth\n");
4657
4715
  return [];
4658
4716
  }
4659
4717
  console.log("Logged in. Scanning sessions...");
4718
+ await page.waitForTimeout(3e3);
4660
4719
  const sessions = [];
4661
- const sessionLinks = await page.$$(ClaudeSelectors.sidebar.allSessions);
4720
+ const sessionItems = await page.$$(ClaudeSelectors.sidebar.allSessions);
4662
4721
  if (options.verbose) {
4663
- console.log(`Found ${sessionLinks.length} session links in sidebar`);
4722
+ console.log(`Found ${sessionItems.length} session items in sidebar`);
4664
4723
  }
4665
- const limit = Math.min(sessionLinks.length, options.limit);
4724
+ const limit = Math.min(sessionItems.length, options.limit);
4666
4725
  for (let i = 0; i < limit; i++) {
4667
- const links = await page.$$(ClaudeSelectors.sidebar.allSessions);
4668
- if (i >= links.length) break;
4669
- const link = links[i];
4726
+ const items = await page.$$(ClaudeSelectors.sidebar.allSessions);
4727
+ if (i >= items.length) break;
4728
+ const item = items[i];
4670
4729
  try {
4671
- const href = await link.getAttribute("href");
4672
- const title = await link.textContent() || "Untitled";
4673
- if (!href) continue;
4674
- const sessionUrl = `https://claude.ai${href}`;
4730
+ const titleElement = await item.$("span.text-text-100");
4731
+ const title = titleElement ? (await titleElement.textContent())?.trim() || "Untitled" : "Untitled";
4732
+ let repoName;
4733
+ try {
4734
+ const repoElement = await item.$("span.text-text-500 span.truncate");
4735
+ if (repoElement) {
4736
+ const repoText = (await repoElement.textContent())?.trim();
4737
+ if (repoText && repoText.length > 0) {
4738
+ repoName = repoText;
4739
+ }
4740
+ }
4741
+ } catch {
4742
+ }
4675
4743
  if (options.verbose) {
4676
- process.stdout.write(`\r Scanning ${i + 1}/${limit}: ${title.slice(0, 40)}...`);
4744
+ const repoDisplay = repoName ? ` (${repoName})` : "";
4745
+ process.stdout.write(`\r Scanning ${i + 1}/${limit}: ${title.slice(0, 35)}${repoDisplay}...`);
4677
4746
  }
4678
- await link.click();
4679
- await page.waitForTimeout(1500);
4747
+ try {
4748
+ await page.keyboard.press("Escape");
4749
+ await page.waitForTimeout(300);
4750
+ } catch {
4751
+ }
4752
+ await item.click();
4753
+ await page.waitForTimeout(2e3);
4754
+ const sessionUrl = page.url();
4680
4755
  let branchName;
4681
4756
  try {
4682
4757
  const branchElement = await page.$(ClaudeSelectors.branchBar.branchName);
@@ -4685,20 +4760,6 @@ async function scanClaudeSessions(options) {
4685
4760
  }
4686
4761
  } catch {
4687
4762
  }
4688
- let repoName;
4689
- try {
4690
- const repoElement = await page.$(ClaudeSelectors.repository.repoName);
4691
- if (repoElement) {
4692
- const repoText = await repoElement.textContent();
4693
- if (repoText) {
4694
- const parsed = parseRepoName(repoText);
4695
- if (parsed) {
4696
- repoName = `${parsed.owner}/${parsed.repo}`;
4697
- }
4698
- }
4699
- }
4700
- } catch {
4701
- }
4702
4763
  const archived = !!await page.$(ClaudeSelectors.archive.indicator);
4703
4764
  sessions.push({
4704
4765
  url: sessionUrl,