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.
- package/main.js +199 -138
- package/main.js.map +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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
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")
|
|
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
|
|
3331
|
-
|
|
3332
|
-
|
|
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
|
|
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
|
|
3428
|
-
await openForLogin();
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
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
|
-
|
|
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: '
|
|
4396
|
+
newTaskInput: 'textarea[placeholder="Ask Claude to write code..."]',
|
|
4395
4397
|
/** Session list container */
|
|
4396
|
-
sessionList: "
|
|
4398
|
+
sessionList: ".flex.flex-col.gap-0\\.5.px-1",
|
|
4397
4399
|
/** Individual session items - use :has-text() with title */
|
|
4398
|
-
sessionItem: (title) =>
|
|
4399
|
-
/** All session
|
|
4400
|
-
allSessions:
|
|
4401
|
-
/**
|
|
4402
|
-
|
|
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
|
|
4406
|
-
|
|
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 (
|
|
4417
|
+
// Branch Bar (between chat messages and chat input) - Verified 2026-02-23
|
|
4410
4418
|
// ============================================================================
|
|
4411
4419
|
branchBar: {
|
|
4412
|
-
/** Container for branch
|
|
4413
|
-
container:
|
|
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
|
-
/**
|
|
4417
|
-
|
|
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"]
|
|
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: '
|
|
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
|
|
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
|
-
|
|
4459
|
-
/** Model
|
|
4460
|
-
|
|
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) => `
|
|
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
|
|
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
|
|
4469
|
-
|
|
4470
|
-
/**
|
|
4471
|
-
|
|
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
|
-
/**
|
|
4478
|
-
|
|
4479
|
-
/** Repo
|
|
4480
|
-
repoName:
|
|
4481
|
-
/**
|
|
4482
|
-
|
|
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
|
|
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
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
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("
|
|
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(
|
|
4651
|
-
console.log("Navigating to Claude
|
|
4652
|
-
await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded" });
|
|
4653
|
-
|
|
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
|
|
4720
|
+
const sessionItems = await page.$$(ClaudeSelectors.sidebar.allSessions);
|
|
4662
4721
|
if (options.verbose) {
|
|
4663
|
-
console.log(`Found ${
|
|
4722
|
+
console.log(`Found ${sessionItems.length} session items in sidebar`);
|
|
4664
4723
|
}
|
|
4665
|
-
const limit = Math.min(
|
|
4724
|
+
const limit = Math.min(sessionItems.length, options.limit);
|
|
4666
4725
|
for (let i = 0; i < limit; i++) {
|
|
4667
|
-
const
|
|
4668
|
-
if (i >=
|
|
4669
|
-
const
|
|
4726
|
+
const items = await page.$$(ClaudeSelectors.sidebar.allSessions);
|
|
4727
|
+
if (i >= items.length) break;
|
|
4728
|
+
const item = items[i];
|
|
4670
4729
|
try {
|
|
4671
|
-
const
|
|
4672
|
-
const title = await
|
|
4673
|
-
|
|
4674
|
-
|
|
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
|
-
|
|
4744
|
+
const repoDisplay = repoName ? ` (${repoName})` : "";
|
|
4745
|
+
process.stdout.write(`\r Scanning ${i + 1}/${limit}: ${title.slice(0, 35)}${repoDisplay}...`);
|
|
4677
4746
|
}
|
|
4678
|
-
|
|
4679
|
-
|
|
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,
|