flightdesk 0.1.4 → 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 +484 -53
  2. package/main.js.map +4 -4
  3. package/package.json +1 -1
package/main.js CHANGED
@@ -959,7 +959,7 @@ var require_command = __commonJS({
959
959
  var EventEmitter = require("node:events").EventEmitter;
960
960
  var childProcess = require("node:child_process");
961
961
  var path3 = require("node:path");
962
- var fs3 = require("node:fs");
962
+ var fs4 = require("node:fs");
963
963
  var process2 = require("node:process");
964
964
  var { Argument: Argument2, humanReadableArgName } = require_argument();
965
965
  var { CommanderError: CommanderError2 } = require_error();
@@ -1892,10 +1892,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
1892
1892
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1893
1893
  function findFile(baseDir, baseName) {
1894
1894
  const localBin = path3.resolve(baseDir, baseName);
1895
- if (fs3.existsSync(localBin)) return localBin;
1895
+ if (fs4.existsSync(localBin)) return localBin;
1896
1896
  if (sourceExt.includes(path3.extname(baseName))) return void 0;
1897
1897
  const foundExt = sourceExt.find(
1898
- (ext) => fs3.existsSync(`${localBin}${ext}`)
1898
+ (ext) => fs4.existsSync(`${localBin}${ext}`)
1899
1899
  );
1900
1900
  if (foundExt) return `${localBin}${foundExt}`;
1901
1901
  return void 0;
@@ -1907,7 +1907,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1907
1907
  if (this._scriptPath) {
1908
1908
  let resolvedScriptPath;
1909
1909
  try {
1910
- resolvedScriptPath = fs3.realpathSync(this._scriptPath);
1910
+ resolvedScriptPath = fs4.realpathSync(this._scriptPath);
1911
1911
  } catch (err) {
1912
1912
  resolvedScriptPath = this._scriptPath;
1913
1913
  }
@@ -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`, {
@@ -3493,6 +3495,21 @@ var FlightDeskAPI = class {
3493
3495
  const result = await this.graphql(query, { taskId, input });
3494
3496
  return result.userUpdateTask;
3495
3497
  }
3498
+ async registerSession(taskId, input) {
3499
+ const query = `
3500
+ mutation RegisterSession($taskId: String!, $input: RegisterSessionInput!) {
3501
+ userRegisterSession(taskId: $taskId, input: $input) {
3502
+ id
3503
+ title
3504
+ status
3505
+ sessionViewUrl
3506
+ sessionTeleportId
3507
+ }
3508
+ }
3509
+ `;
3510
+ const result = await this.graphql(query, { taskId, input });
3511
+ return result.userRegisterSession;
3512
+ }
3496
3513
  async getTask(taskId) {
3497
3514
  const query = `
3498
3515
  query GetTask($taskId: String!) {
@@ -3551,15 +3568,15 @@ var FlightDeskAPI = class {
3551
3568
  // ============================================================================
3552
3569
  async listProjects() {
3553
3570
  const query = `
3554
- query ListProjects {
3555
- userProjects {
3571
+ query ListProjects($organizationId: String!) {
3572
+ userProjects(organizationId: $organizationId) {
3556
3573
  id
3557
3574
  name
3558
3575
  githubRepo
3559
3576
  }
3560
3577
  }
3561
3578
  `;
3562
- const result = await this.graphql(query);
3579
+ const result = await this.graphql(query, { organizationId: this.organizationId });
3563
3580
  return result.userProjects;
3564
3581
  }
3565
3582
  async getProject(projectId) {
@@ -3576,6 +3593,21 @@ var FlightDeskAPI = class {
3576
3593
  const result = await this.graphql(query, { projectId });
3577
3594
  return result.userProject;
3578
3595
  }
3596
+ async createProject(input) {
3597
+ const query = `
3598
+ mutation CreateProject($input: UserCreateProjectInput!) {
3599
+ userCreateProject(input: $input) {
3600
+ id
3601
+ name
3602
+ githubRepo
3603
+ organizationId
3604
+ createdAt
3605
+ }
3606
+ }
3607
+ `;
3608
+ const result = await this.graphql(query, { input });
3609
+ return result.userCreateProject;
3610
+ }
3579
3611
  // ============================================================================
3580
3612
  // Task Token Operations (for agent authentication)
3581
3613
  // ============================================================================
@@ -3872,7 +3904,7 @@ Watching... (Ctrl+C to stop)
3872
3904
  clearInterval(intervalId);
3873
3905
  process.exit(0);
3874
3906
  });
3875
- await new Promise(() => {
3907
+ await new Promise((_resolve) => {
3876
3908
  });
3877
3909
  }
3878
3910
  async function processSessionInfo(api, task2, info) {
@@ -4246,7 +4278,7 @@ async function orgSetDefaultCommand(orgId) {
4246
4278
  var import_child_process = require("child_process");
4247
4279
  async function contextCommand() {
4248
4280
  console.log("\n\u{1F50D} Current Context\n");
4249
- let repoInfo = {};
4281
+ const repoInfo = {};
4250
4282
  try {
4251
4283
  const remoteUrl = (0, import_child_process.execSync)("git remote get-url origin", {
4252
4284
  encoding: "utf-8",
@@ -4354,9 +4386,407 @@ async function syncCommand() {
4354
4386
  }
4355
4387
  }
4356
4388
 
4389
+ // apps/cli/src/lib/claude-selectors.ts
4390
+ var ClaudeSelectors = {
4391
+ // ============================================================================
4392
+ // Sidebar - Session List (Verified 2026-02-23)
4393
+ // ============================================================================
4394
+ sidebar: {
4395
+ /** New task input (textarea in sidebar) */
4396
+ newTaskInput: 'textarea[placeholder="Ask Claude to write code..."]',
4397
+ /** Session list container */
4398
+ sessionList: ".flex.flex-col.gap-0\\.5.px-1",
4399
+ /** Individual session items - use :has-text() with title */
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",
4407
+ /** Session title within a session item */
4408
+ sessionTitle: "button.text-sm.font-medium.truncate",
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"]'
4415
+ },
4416
+ // ============================================================================
4417
+ // Branch Bar (between chat messages and chat input) - Verified 2026-02-23
4418
+ // ============================================================================
4419
+ branchBar: {
4420
+ /** Container for branch bar */
4421
+ container: ".flex.items-center.gap-2.w-full.p-2",
4422
+ /** Branch name display - the copyable span */
4423
+ branchName: "button.group\\/copy span.truncate",
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"
4430
+ },
4431
+ // ============================================================================
4432
+ // Pull Request Controls - Verified 2026-02-23
4433
+ // ============================================================================
4434
+ pullRequest: {
4435
+ /** Create PR button (left half - immediate action) - CAREFUL: clicks immediately! */
4436
+ createPrButton: 'button:has-text("Create PR"):not([aria-haspopup])',
4437
+ /** PR dropdown trigger (right half - shows options) - SAFE to click */
4438
+ prDropdown: 'button.rounded-r-md[aria-haspopup="menu"]',
4439
+ /** PR status indicator when PR exists */
4440
+ prStatus: 'a[href*="/pull/"], a[href*="/merge_requests/"]',
4441
+ /** View PR link */
4442
+ viewPrLink: 'a:has-text("View PR"), a:has-text("View Pull Request")'
4443
+ },
4444
+ // ============================================================================
4445
+ // Chat Area - Verified 2026-02-23
4446
+ // ============================================================================
4447
+ chat: {
4448
+ /** Main chat container */
4449
+ container: "main",
4450
+ /** Chat messages */
4451
+ messages: 'div[data-testid="chat-message"], div.prose',
4452
+ /** User messages */
4453
+ userMessages: 'div[data-testid="user-message"]',
4454
+ /** Assistant messages */
4455
+ assistantMessages: 'div[data-testid="assistant-message"]',
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"]',
4462
+ /** Send button */
4463
+ sendButton: 'form.w-full button[aria-label="Submit"]',
4464
+ /** Stop button (during generation) */
4465
+ stopButton: 'button:has-text("Stop")'
4466
+ },
4467
+ // ============================================================================
4468
+ // Model Selector - Verified 2026-02-23
4469
+ // ============================================================================
4470
+ model: {
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"]',
4477
+ /** Specific model option */
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")'
4483
+ },
4484
+ // ============================================================================
4485
+ // Mode Toggle - Verified 2026-02-23
4486
+ // Text alternates between "Auto accept edits" and "Plan mode"
4487
+ // ============================================================================
4488
+ mode: {
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")'
4493
+ },
4494
+ // ============================================================================
4495
+ // Repository Connection - Verified 2026-02-23
4496
+ // ============================================================================
4497
+ repository: {
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")'
4506
+ },
4507
+ // ============================================================================
4508
+ // Session Archive
4509
+ // ============================================================================
4510
+ archive: {
4511
+ /** Archive indicator text */
4512
+ indicator: "text=This session has been archived",
4513
+ /** Archive button (if available) */
4514
+ archiveButton: 'button:has-text("Archive")'
4515
+ },
4516
+ // ============================================================================
4517
+ // Authentication
4518
+ // ============================================================================
4519
+ auth: {
4520
+ /** Login page indicators */
4521
+ loginPage: 'text=Log in, text=Sign in, form[action*="login"]',
4522
+ /** OAuth redirect */
4523
+ oauthRedirect: "text=Redirecting"
4524
+ },
4525
+ // ============================================================================
4526
+ // Dropdowns (Radix UI pattern)
4527
+ // ============================================================================
4528
+ dropdown: {
4529
+ /** Open dropdown menu */
4530
+ menu: 'div[role="menu"]',
4531
+ /** Menu items */
4532
+ items: 'div[role="menuitem"]',
4533
+ /** Specific menu item */
4534
+ item: (text) => `div[role="menuitem"]:has-text("${text}")`,
4535
+ /** Listbox (for selects) */
4536
+ listbox: 'div[role="listbox"]',
4537
+ /** Listbox options */
4538
+ listboxOptions: 'div[role="option"]'
4539
+ }
4540
+ };
4541
+
4542
+ // apps/cli/src/commands/import.ts
4543
+ var fs3 = __toESM(require("fs"));
4544
+ var playwright2 = null;
4545
+ async function importCommand(options) {
4546
+ if (!isConfigured()) {
4547
+ console.error("FlightDesk is not configured. Run: flightdesk init");
4548
+ process.exit(1);
4549
+ }
4550
+ const org2 = getDefaultOrganization();
4551
+ if (!org2) {
4552
+ console.error("No organization configured. Run: flightdesk init");
4553
+ process.exit(1);
4554
+ }
4555
+ if (!await isPlaywrightAvailable()) {
4556
+ console.error("Playwright is required for import. Install it with:");
4557
+ console.error(" npm install -g playwright");
4558
+ console.error(" npx playwright install chromium");
4559
+ process.exit(1);
4560
+ }
4561
+ playwright2 = await import("playwright");
4562
+ const api = new FlightDeskAPI(org2);
4563
+ console.log("\n\u{1F50D} FlightDesk Import - Scanning Claude Sessions\n");
4564
+ if (options.dryRun) {
4565
+ console.log("\u{1F4CB} DRY RUN - No changes will be made\n");
4566
+ }
4567
+ let existingProjects = [];
4568
+ try {
4569
+ existingProjects = await api.listProjects();
4570
+ if (options.verbose) {
4571
+ console.log(`Found ${existingProjects.length} existing projects`);
4572
+ }
4573
+ } catch (error) {
4574
+ console.error("Warning: Could not fetch existing projects:", error);
4575
+ }
4576
+ const sessions = await scanClaudeSessions({
4577
+ headless: options.headless !== false,
4578
+ limit: options.limit || 50,
4579
+ verbose: options.verbose
4580
+ });
4581
+ if (sessions.length === 0) {
4582
+ console.log("No sessions found. Make sure you are logged into Claude.");
4583
+ console.log("Run: flightdesk auth");
4584
+ return;
4585
+ }
4586
+ console.log(`
4587
+ Found ${sessions.length} sessions:
4588
+ `);
4589
+ const sessionsByRepo = /* @__PURE__ */ new Map();
4590
+ const noRepoSessions = [];
4591
+ for (const session of sessions) {
4592
+ if (session.repoName) {
4593
+ const existing = sessionsByRepo.get(session.repoName) || [];
4594
+ existing.push(session);
4595
+ sessionsByRepo.set(session.repoName, existing);
4596
+ } else {
4597
+ noRepoSessions.push(session);
4598
+ }
4599
+ }
4600
+ for (const [repoName, repoSessions] of sessionsByRepo) {
4601
+ const matchingProject = existingProjects.find(
4602
+ (p) => p.githubRepo && p.githubRepo.endsWith(`/${repoName}`)
4603
+ );
4604
+ if (matchingProject) {
4605
+ console.log(`\u{1F4C1} ${repoName} \u2192 Project: ${matchingProject.name} (${matchingProject.id.slice(0, 8)})`);
4606
+ } else {
4607
+ console.log(`\u{1F4C1} ${repoName} \u2192 No matching project`);
4608
+ }
4609
+ for (const session of repoSessions) {
4610
+ const status = session.archived ? "\u{1F4E6}" : session.branchName ? "\u{1F33F}" : "\u{1F4AC}";
4611
+ console.log(` ${status} ${session.title.slice(0, 50)}${session.title.length > 50 ? "..." : ""}`);
4612
+ if (session.branchName) {
4613
+ console.log(` Branch: ${session.branchName}`);
4614
+ }
4615
+ }
4616
+ console.log("");
4617
+ }
4618
+ if (noRepoSessions.length > 0) {
4619
+ console.log(`\u{1F4C1} No Repository (${noRepoSessions.length} sessions)`);
4620
+ for (const session of noRepoSessions.slice(0, 5)) {
4621
+ const status = session.archived ? "\u{1F4E6}" : "\u{1F4AC}";
4622
+ console.log(` ${status} ${session.title.slice(0, 50)}${session.title.length > 50 ? "..." : ""}`);
4623
+ }
4624
+ if (noRepoSessions.length > 5) {
4625
+ console.log(` ... and ${noRepoSessions.length - 5} more`);
4626
+ }
4627
+ console.log("");
4628
+ }
4629
+ console.log("\u2500".repeat(60));
4630
+ console.log(`
4631
+ Summary:`);
4632
+ console.log(` Total sessions: ${sessions.length}`);
4633
+ console.log(` With repository: ${sessions.length - noRepoSessions.length}`);
4634
+ console.log(` Without repository: ${noRepoSessions.length}`);
4635
+ console.log(` Archived: ${sessions.filter((s) => s.archived).length}`);
4636
+ console.log("");
4637
+ if (options.dryRun) {
4638
+ console.log("\u{1F4A1} Run without --dry-run to create tasks in FlightDesk");
4639
+ return;
4640
+ }
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
+ }
4680
+ console.log("");
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}`);
4687
+ }
4688
+ async function scanClaudeSessions(options) {
4689
+ if (!playwright2) {
4690
+ throw new Error("Playwright not loaded");
4691
+ }
4692
+ if (!fs3.existsSync(USER_DATA_DIR)) {
4693
+ fs3.mkdirSync(USER_DATA_DIR, { recursive: true });
4694
+ }
4695
+ const context = await playwright2.chromium.launchPersistentContext(USER_DATA_DIR, {
4696
+ headless: options.headless,
4697
+ viewport: { width: 1280, height: 720 },
4698
+ userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
4699
+ });
4700
+ try {
4701
+ const page = await context.newPage();
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);
4709
+ const url = page.url();
4710
+ if (options.verbose) {
4711
+ console.log("Current URL:", url);
4712
+ }
4713
+ if (url.includes("/login") || url.includes("/oauth")) {
4714
+ console.log("\n\u26A0\uFE0F Not logged into Claude. Run: flightdesk auth\n");
4715
+ return [];
4716
+ }
4717
+ console.log("Logged in. Scanning sessions...");
4718
+ await page.waitForTimeout(3e3);
4719
+ const sessions = [];
4720
+ const sessionItems = await page.$$(ClaudeSelectors.sidebar.allSessions);
4721
+ if (options.verbose) {
4722
+ console.log(`Found ${sessionItems.length} session items in sidebar`);
4723
+ }
4724
+ const limit = Math.min(sessionItems.length, options.limit);
4725
+ for (let i = 0; i < limit; i++) {
4726
+ const items = await page.$$(ClaudeSelectors.sidebar.allSessions);
4727
+ if (i >= items.length) break;
4728
+ const item = items[i];
4729
+ try {
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
+ }
4743
+ if (options.verbose) {
4744
+ const repoDisplay = repoName ? ` (${repoName})` : "";
4745
+ process.stdout.write(`\r Scanning ${i + 1}/${limit}: ${title.slice(0, 35)}${repoDisplay}...`);
4746
+ }
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();
4755
+ let branchName;
4756
+ try {
4757
+ const branchElement = await page.$(ClaudeSelectors.branchBar.branchName);
4758
+ if (branchElement) {
4759
+ branchName = (await branchElement.textContent())?.trim();
4760
+ }
4761
+ } catch {
4762
+ }
4763
+ const archived = !!await page.$(ClaudeSelectors.archive.indicator);
4764
+ sessions.push({
4765
+ url: sessionUrl,
4766
+ title: title.trim(),
4767
+ branchName,
4768
+ repoName,
4769
+ archived
4770
+ });
4771
+ } catch (error) {
4772
+ if (options.verbose) {
4773
+ console.error(`
4774
+ Error scanning session ${i + 1}:`, error);
4775
+ }
4776
+ }
4777
+ }
4778
+ if (options.verbose) {
4779
+ console.log("\n");
4780
+ }
4781
+ return sessions;
4782
+ } finally {
4783
+ await context.close();
4784
+ }
4785
+ }
4786
+
4357
4787
  // apps/cli/src/main.ts
4358
4788
  var program2 = new Command();
4359
- program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.1.0");
4789
+ program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.1.5");
4360
4790
  program2.command("init").description("Configure FlightDesk CLI with your API credentials").action(initCommand);
4361
4791
  program2.command("auth").description("Log in to Claude for session monitoring").action(authCommand);
4362
4792
  program2.command("register <project-id> [task-id]").description("Register a Claude Code session with a FlightDesk task").option("--view-url <url>", "Claude Code session view URL").option("--teleport-id <id>", "Claude Code teleport ID").option("--title <title>", "Task title (creates new task if task-id not provided)").option("--description <description>", "Task description").action(registerCommand);
@@ -4366,6 +4796,7 @@ task.command("list").description("List tasks").option("-p, --project <id>", "Fil
4366
4796
  task.command("status <task-id>").description("Get task status").action((taskId) => taskCommand("status", { taskId }));
4367
4797
  task.command("update <task-id>").description("Update task").option("-s, --status <status>", "New status").option("--branch <branch>", "Branch name").option("--pr-url <url>", "Pull request URL").action((taskId, options) => taskCommand("update", { taskId, ...options }));
4368
4798
  program2.command("watch").description("Start the Playwright daemon to monitor Claude Code sessions").option("--interval <minutes>", "Check interval in minutes", "5").option("--once", "Run once and exit").option("--auto-pr", 'Automatically click "Create PR" button when found').option("--no-headless", "Run browser in visible mode (for debugging)").action(watchCommand);
4799
+ program2.command("import").description("Scan Claude.ai for existing sessions and import them as tasks").option("--dry-run", "Show what would be imported without making changes").option("--limit <n>", "Maximum number of sessions to scan", "50").option("-p, --project <id>", "Import all sessions into a specific project").option("--no-headless", "Run browser in visible mode (for debugging)").option("-v, --verbose", "Show detailed progress").action(importCommand);
4369
4800
  program2.command("status").description("Show status of all active tasks").option("-p, --project <id>", "Filter by project").action(statusCommand);
4370
4801
  program2.command("prompt <task-id>").description("Get a prompt for a task (ready to paste into Claude)").option("--type <type>", "Prompt type: review, test_plan, summary, handoff", "review").action(promptCommand);
4371
4802
  var org = program2.command("org").description("Organization management");