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.
- package/main.js +484 -53
- package/main.js.map +4 -4
- 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
|
|
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 (
|
|
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) =>
|
|
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 =
|
|
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
|
-
|
|
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`, {
|
|
@@ -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
|
-
|
|
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.
|
|
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");
|