flightdesk 0.1.5 → 0.1.7
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 +236 -153
- package/main.js.map +4 -4
- package/package.json +1 -1
package/main.js
CHANGED
|
@@ -3035,15 +3035,25 @@ var {
|
|
|
3035
3035
|
Help
|
|
3036
3036
|
} = import_index.default;
|
|
3037
3037
|
|
|
3038
|
-
// apps/cli/src/commands/init.ts
|
|
3039
|
-
var readline = __toESM(require("readline"));
|
|
3040
|
-
|
|
3041
3038
|
// apps/cli/src/lib/config.ts
|
|
3042
3039
|
var fs = __toESM(require("fs"));
|
|
3043
3040
|
var path = __toESM(require("path"));
|
|
3044
3041
|
var os = __toESM(require("os"));
|
|
3045
3042
|
var CONFIG_FILE = path.join(os.homedir(), ".flightdeskrc");
|
|
3046
|
-
var DEFAULT_API_URL = "https://flightdesk.dev
|
|
3043
|
+
var DEFAULT_API_URL = "https://api.flightdesk.dev";
|
|
3044
|
+
var DEV_API_URL = "http://localhost:3000";
|
|
3045
|
+
var apiUrlOverride = null;
|
|
3046
|
+
function setDevMode(enabled) {
|
|
3047
|
+
if (enabled) {
|
|
3048
|
+
apiUrlOverride = DEV_API_URL;
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
function setApiUrl(url) {
|
|
3052
|
+
apiUrlOverride = url;
|
|
3053
|
+
}
|
|
3054
|
+
function getApiUrl() {
|
|
3055
|
+
return apiUrlOverride ?? DEFAULT_API_URL;
|
|
3056
|
+
}
|
|
3047
3057
|
function loadConfig() {
|
|
3048
3058
|
try {
|
|
3049
3059
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
@@ -3111,6 +3121,7 @@ function isConfigured() {
|
|
|
3111
3121
|
}
|
|
3112
3122
|
|
|
3113
3123
|
// apps/cli/src/commands/init.ts
|
|
3124
|
+
var readline = __toESM(require("readline"));
|
|
3114
3125
|
function question(rl, prompt) {
|
|
3115
3126
|
return new Promise((resolve) => {
|
|
3116
3127
|
rl.question(prompt, (answer) => {
|
|
@@ -3151,8 +3162,8 @@ async function initCommand() {
|
|
|
3151
3162
|
rl.close();
|
|
3152
3163
|
return;
|
|
3153
3164
|
}
|
|
3154
|
-
const
|
|
3155
|
-
|
|
3165
|
+
const apiUrl = getApiUrl();
|
|
3166
|
+
console.log(`Using API: ${apiUrl}`);
|
|
3156
3167
|
let orgId;
|
|
3157
3168
|
const keyMatch = apiKey.match(/^fd_key_([^_]+)_/);
|
|
3158
3169
|
if (keyMatch) {
|
|
@@ -3211,9 +3222,10 @@ async function initCommand() {
|
|
|
3211
3222
|
}
|
|
3212
3223
|
|
|
3213
3224
|
// apps/cli/src/lib/session-monitor.ts
|
|
3214
|
-
var path2 = __toESM(require("path"));
|
|
3215
|
-
var os2 = __toESM(require("os"));
|
|
3216
|
-
var fs2 = __toESM(require("fs"));
|
|
3225
|
+
var path2 = __toESM(require("node:path"));
|
|
3226
|
+
var os2 = __toESM(require("node:os"));
|
|
3227
|
+
var fs2 = __toESM(require("node:fs"));
|
|
3228
|
+
var readline2 = __toESM(require("node:readline"));
|
|
3217
3229
|
var playwright = null;
|
|
3218
3230
|
var USER_DATA_DIR = path2.join(os2.homedir(), ".flightdesk", "chromium-profile");
|
|
3219
3231
|
async function isPlaywrightAvailable() {
|
|
@@ -3249,10 +3261,16 @@ async function checkAuth() {
|
|
|
3249
3261
|
const { context } = await launchBrowser(true);
|
|
3250
3262
|
try {
|
|
3251
3263
|
const page = await context.newPage();
|
|
3252
|
-
|
|
3264
|
+
page.setDefaultTimeout(3e4);
|
|
3265
|
+
await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
3266
|
+
await page.waitForTimeout(3e3);
|
|
3253
3267
|
const url = page.url();
|
|
3254
|
-
|
|
3268
|
+
console.log(" Final URL:", url);
|
|
3269
|
+
const isLoggedIn = !url.includes("/login") && !url.includes("/oauth") && !url.includes("accounts.google") && url.includes("claude.ai");
|
|
3255
3270
|
return isLoggedIn;
|
|
3271
|
+
} catch (error) {
|
|
3272
|
+
console.error("Auth check failed:", error.message);
|
|
3273
|
+
return false;
|
|
3256
3274
|
} finally {
|
|
3257
3275
|
await context.close();
|
|
3258
3276
|
}
|
|
@@ -3262,15 +3280,20 @@ async function openForLogin() {
|
|
|
3262
3280
|
throw new Error("Playwright not installed");
|
|
3263
3281
|
}
|
|
3264
3282
|
console.log("Opening browser for Claude login...");
|
|
3265
|
-
console.log("Please log in, then close the browser window.");
|
|
3266
3283
|
const { context } = await launchBrowser(false);
|
|
3267
3284
|
try {
|
|
3268
3285
|
const page = await context.newPage();
|
|
3269
|
-
await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded" });
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3286
|
+
await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded", timeout: 6e4 });
|
|
3287
|
+
console.log("\n\u{1F449} Press ENTER here when you have logged in...\n");
|
|
3288
|
+
await waitForEnter();
|
|
3289
|
+
console.log("Verifying login in current session...");
|
|
3290
|
+
await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
3291
|
+
await page.waitForTimeout(2e3);
|
|
3292
|
+
const url = page.url();
|
|
3293
|
+
console.log(" Final URL:", url);
|
|
3294
|
+
const isLoggedIn = !url.includes("/login") && !url.includes("/oauth") && !url.includes("accounts.google") && url.includes("claude.ai");
|
|
3295
|
+
console.log("Closing browser...");
|
|
3296
|
+
return isLoggedIn;
|
|
3274
3297
|
} finally {
|
|
3275
3298
|
try {
|
|
3276
3299
|
await context.close();
|
|
@@ -3278,6 +3301,18 @@ async function openForLogin() {
|
|
|
3278
3301
|
}
|
|
3279
3302
|
}
|
|
3280
3303
|
}
|
|
3304
|
+
function waitForEnter() {
|
|
3305
|
+
return new Promise((resolve) => {
|
|
3306
|
+
const rl = readline2.createInterface({
|
|
3307
|
+
input: process.stdin,
|
|
3308
|
+
output: process.stdout
|
|
3309
|
+
});
|
|
3310
|
+
rl.question("", () => {
|
|
3311
|
+
rl.close();
|
|
3312
|
+
resolve();
|
|
3313
|
+
});
|
|
3314
|
+
});
|
|
3315
|
+
}
|
|
3281
3316
|
async function monitorSession(sessionUrl, options = {}) {
|
|
3282
3317
|
if (!await isPlaywrightAvailable()) {
|
|
3283
3318
|
return { status: "error", error: "Playwright not installed" };
|
|
@@ -3305,7 +3340,7 @@ async function monitorSession(sessionUrl, options = {}) {
|
|
|
3305
3340
|
if (branchName) {
|
|
3306
3341
|
result.branchName = branchName;
|
|
3307
3342
|
}
|
|
3308
|
-
const prButton = await page.$('button:has-text("Create PR")
|
|
3343
|
+
const prButton = await page.$('button:has-text("Create PR"):not([aria-haspopup])');
|
|
3309
3344
|
result.hasPrButton = !!prButton;
|
|
3310
3345
|
if (autoPr && prButton) {
|
|
3311
3346
|
console.log(' Clicking "Create PR" button...');
|
|
@@ -3327,21 +3362,18 @@ async function monitorSession(sessionUrl, options = {}) {
|
|
|
3327
3362
|
}
|
|
3328
3363
|
}
|
|
3329
3364
|
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"
|
|
3365
|
+
const primarySelectors = [
|
|
3366
|
+
String.raw`button.group\/copy span.truncate`
|
|
3367
|
+
// New branch name (verified)
|
|
3336
3368
|
];
|
|
3337
|
-
for (const selector of
|
|
3369
|
+
for (const selector of primarySelectors) {
|
|
3338
3370
|
try {
|
|
3339
3371
|
const element = await page.$(selector);
|
|
3340
3372
|
if (element) {
|
|
3341
3373
|
const text = await element.textContent();
|
|
3342
3374
|
if (text) {
|
|
3343
3375
|
const cleaned = text.trim();
|
|
3344
|
-
if (cleaned && !cleaned.includes(" ")) {
|
|
3376
|
+
if (cleaned && cleaned.length > 2 && !cleaned.includes(" ")) {
|
|
3345
3377
|
return cleaned;
|
|
3346
3378
|
}
|
|
3347
3379
|
}
|
|
@@ -3349,24 +3381,6 @@ async function extractBranchName(page) {
|
|
|
3349
3381
|
} catch {
|
|
3350
3382
|
}
|
|
3351
3383
|
}
|
|
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
3384
|
return null;
|
|
3371
3385
|
}
|
|
3372
3386
|
async function extractPrUrl(page) {
|
|
@@ -3424,15 +3438,13 @@ async function authCommand() {
|
|
|
3424
3438
|
}
|
|
3425
3439
|
console.log("\u274C Not logged in to Claude.\n");
|
|
3426
3440
|
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.");
|
|
3441
|
+
console.log("Please log in to your Claude account.\n");
|
|
3442
|
+
const loginSuccessful = await openForLogin();
|
|
3443
|
+
if (loginSuccessful) {
|
|
3444
|
+
console.log("\n\u2705 Successfully logged in!");
|
|
3445
|
+
console.log("The watch daemon can now monitor your Claude Code sessions.");
|
|
3434
3446
|
} else {
|
|
3435
|
-
console.log("\u274C Login was not detected.");
|
|
3447
|
+
console.log("\n\u274C Login was not detected.");
|
|
3436
3448
|
console.log("Please try again with: flightdesk auth");
|
|
3437
3449
|
}
|
|
3438
3450
|
}
|
|
@@ -3442,6 +3454,7 @@ var FlightDeskAPI = class {
|
|
|
3442
3454
|
constructor(org2) {
|
|
3443
3455
|
this.apiUrl = org2.apiUrl;
|
|
3444
3456
|
this.apiKey = org2.apiKey;
|
|
3457
|
+
this.organizationId = org2.id;
|
|
3445
3458
|
}
|
|
3446
3459
|
async graphql(query, variables) {
|
|
3447
3460
|
const response = await fetch(`${this.apiUrl}/graphql`, {
|
|
@@ -3566,15 +3579,15 @@ var FlightDeskAPI = class {
|
|
|
3566
3579
|
// ============================================================================
|
|
3567
3580
|
async listProjects() {
|
|
3568
3581
|
const query = `
|
|
3569
|
-
query ListProjects {
|
|
3570
|
-
userProjects {
|
|
3582
|
+
query ListProjects($organizationId: String!) {
|
|
3583
|
+
userProjects(organizationId: $organizationId) {
|
|
3571
3584
|
id
|
|
3572
3585
|
name
|
|
3573
3586
|
githubRepo
|
|
3574
3587
|
}
|
|
3575
3588
|
}
|
|
3576
3589
|
`;
|
|
3577
|
-
const result = await this.graphql(query);
|
|
3590
|
+
const result = await this.graphql(query, { organizationId: this.organizationId });
|
|
3578
3591
|
return result.userProjects;
|
|
3579
3592
|
}
|
|
3580
3593
|
async getProject(projectId) {
|
|
@@ -3902,7 +3915,7 @@ Watching... (Ctrl+C to stop)
|
|
|
3902
3915
|
clearInterval(intervalId);
|
|
3903
3916
|
process.exit(0);
|
|
3904
3917
|
});
|
|
3905
|
-
await new Promise(() => {
|
|
3918
|
+
await new Promise((_resolve) => {
|
|
3906
3919
|
});
|
|
3907
3920
|
}
|
|
3908
3921
|
async function processSessionInfo(api, task2, info) {
|
|
@@ -4128,7 +4141,7 @@ async function promptCommand(taskId, options) {
|
|
|
4128
4141
|
}
|
|
4129
4142
|
|
|
4130
4143
|
// apps/cli/src/commands/org.ts
|
|
4131
|
-
var
|
|
4144
|
+
var readline3 = __toESM(require("readline"));
|
|
4132
4145
|
function question2(rl, prompt) {
|
|
4133
4146
|
return new Promise((resolve) => {
|
|
4134
4147
|
rl.question(prompt, (answer) => {
|
|
@@ -4163,7 +4176,7 @@ async function orgListCommand() {
|
|
|
4163
4176
|
}
|
|
4164
4177
|
}
|
|
4165
4178
|
async function orgAddCommand() {
|
|
4166
|
-
const rl =
|
|
4179
|
+
const rl = readline3.createInterface({
|
|
4167
4180
|
input: process.stdin,
|
|
4168
4181
|
output: process.stdout
|
|
4169
4182
|
});
|
|
@@ -4179,8 +4192,8 @@ async function orgAddCommand() {
|
|
|
4179
4192
|
console.error("API Key is required");
|
|
4180
4193
|
return;
|
|
4181
4194
|
}
|
|
4182
|
-
const
|
|
4183
|
-
|
|
4195
|
+
const apiUrl = getApiUrl();
|
|
4196
|
+
console.log(`Using API: ${apiUrl}`);
|
|
4184
4197
|
let orgId;
|
|
4185
4198
|
const keyMatch = apiKey.match(/^fd_key_([^_]+)_/);
|
|
4186
4199
|
if (keyMatch) {
|
|
@@ -4244,7 +4257,7 @@ async function orgRemoveCommand(orgId) {
|
|
|
4244
4257
|
}
|
|
4245
4258
|
process.exit(1);
|
|
4246
4259
|
}
|
|
4247
|
-
const rl =
|
|
4260
|
+
const rl = readline3.createInterface({
|
|
4248
4261
|
input: process.stdin,
|
|
4249
4262
|
output: process.stdout
|
|
4250
4263
|
});
|
|
@@ -4276,7 +4289,7 @@ async function orgSetDefaultCommand(orgId) {
|
|
|
4276
4289
|
var import_child_process = require("child_process");
|
|
4277
4290
|
async function contextCommand() {
|
|
4278
4291
|
console.log("\n\u{1F50D} Current Context\n");
|
|
4279
|
-
|
|
4292
|
+
const repoInfo = {};
|
|
4280
4293
|
try {
|
|
4281
4294
|
const remoteUrl = (0, import_child_process.execSync)("git remote get-url origin", {
|
|
4282
4295
|
encoding: "utf-8",
|
|
@@ -4387,52 +4400,60 @@ async function syncCommand() {
|
|
|
4387
4400
|
// apps/cli/src/lib/claude-selectors.ts
|
|
4388
4401
|
var ClaudeSelectors = {
|
|
4389
4402
|
// ============================================================================
|
|
4390
|
-
// Sidebar - Session List
|
|
4403
|
+
// Sidebar - Session List (Verified 2026-02-23)
|
|
4391
4404
|
// ============================================================================
|
|
4392
4405
|
sidebar: {
|
|
4393
4406
|
/** New task input (textarea in sidebar) */
|
|
4394
|
-
newTaskInput: '
|
|
4407
|
+
newTaskInput: 'textarea[placeholder="Ask Claude to write code..."]',
|
|
4395
4408
|
/** Session list container */
|
|
4396
|
-
sessionList:
|
|
4409
|
+
sessionList: String.raw`.flex.flex-col.gap-0\.5.px-1`,
|
|
4397
4410
|
/** Individual session items - use :has-text() with title */
|
|
4398
|
-
sessionItem: (title) =>
|
|
4399
|
-
/** All session
|
|
4400
|
-
allSessions:
|
|
4401
|
-
/**
|
|
4402
|
-
|
|
4411
|
+
sessionItem: (title) => `.cursor-pointer:has-text("${title}")`,
|
|
4412
|
+
/** All session items (div elements with cursor-pointer class) */
|
|
4413
|
+
allSessions: String.raw`.flex.flex-col.gap-0\.5.px-1 .cursor-pointer`,
|
|
4414
|
+
/** Session by index (0-indexed) */
|
|
4415
|
+
sessionByIndex: (index) => String.raw`.flex.flex-col.gap-0\.5.px-1` + ` > div:nth-child(${index + 1}) .cursor-pointer`,
|
|
4416
|
+
/** Active/selected session (has bg-bg-300 class) */
|
|
4417
|
+
activeSession: ".cursor-pointer.bg-bg-300",
|
|
4403
4418
|
/** Session title within a session item */
|
|
4404
4419
|
sessionTitle: "button.text-sm.font-medium.truncate",
|
|
4405
|
-
/** Session
|
|
4406
|
-
|
|
4420
|
+
/** Session archive button (icon-only, no aria-label) */
|
|
4421
|
+
sessionArchiveButton: (title) => `.cursor-pointer:has-text("${title}") button`,
|
|
4422
|
+
/** Search sessions button */
|
|
4423
|
+
searchButton: 'button[aria-label="Search \u2318K"]',
|
|
4424
|
+
/** User profile button */
|
|
4425
|
+
userProfileButton: '[data-testid="code-user-menu-button"]'
|
|
4407
4426
|
},
|
|
4408
4427
|
// ============================================================================
|
|
4409
|
-
// Branch Bar (
|
|
4428
|
+
// Branch Bar (between chat messages and chat input) - Verified 2026-02-23
|
|
4410
4429
|
// ============================================================================
|
|
4411
4430
|
branchBar: {
|
|
4412
|
-
/** Container for branch
|
|
4413
|
-
container:
|
|
4431
|
+
/** Container for branch bar */
|
|
4432
|
+
container: ".flex.items-center.gap-2.w-full.p-2",
|
|
4414
4433
|
/** Branch name display - the copyable span */
|
|
4415
4434
|
branchName: "button.group\\/copy span.truncate",
|
|
4416
|
-
/**
|
|
4417
|
-
|
|
4418
|
-
/** Copy branch button */
|
|
4419
|
-
copyBranchButton: "button.group\\/copy"
|
|
4435
|
+
/** Base/target branch dropdown (e.g., "develop") */
|
|
4436
|
+
baseBranchSelector: '.flex.items-center.gap-2.w-full.p-2 button[aria-haspopup="menu"]',
|
|
4437
|
+
/** Copy branch button (click to copy branch name) */
|
|
4438
|
+
copyBranchButton: "button.group\\/copy",
|
|
4439
|
+
/** Diff stats button (+N -M) */
|
|
4440
|
+
diffStatsButton: String.raw`.flex.items-center.gap-2.w-full.p-2 button.border-0\.5`
|
|
4420
4441
|
},
|
|
4421
4442
|
// ============================================================================
|
|
4422
|
-
// Pull Request Controls
|
|
4443
|
+
// Pull Request Controls - Verified 2026-02-23
|
|
4423
4444
|
// ============================================================================
|
|
4424
4445
|
pullRequest: {
|
|
4425
4446
|
/** Create PR button (left half - immediate action) - CAREFUL: clicks immediately! */
|
|
4426
4447
|
createPrButton: 'button:has-text("Create PR"):not([aria-haspopup])',
|
|
4427
|
-
/** PR dropdown trigger (right half - shows options) */
|
|
4428
|
-
prDropdown: 'button[aria-haspopup="menu"]
|
|
4448
|
+
/** PR dropdown trigger (right half - shows options) - SAFE to click */
|
|
4449
|
+
prDropdown: 'button.rounded-r-md[aria-haspopup="menu"]',
|
|
4429
4450
|
/** PR status indicator when PR exists */
|
|
4430
4451
|
prStatus: 'a[href*="/pull/"], a[href*="/merge_requests/"]',
|
|
4431
4452
|
/** View PR link */
|
|
4432
4453
|
viewPrLink: 'a:has-text("View PR"), a:has-text("View Pull Request")'
|
|
4433
4454
|
},
|
|
4434
4455
|
// ============================================================================
|
|
4435
|
-
// Chat Area
|
|
4456
|
+
// Chat Area - Verified 2026-02-23
|
|
4436
4457
|
// ============================================================================
|
|
4437
4458
|
chat: {
|
|
4438
4459
|
/** Main chat container */
|
|
@@ -4443,43 +4464,56 @@ var ClaudeSelectors = {
|
|
|
4443
4464
|
userMessages: 'div[data-testid="user-message"]',
|
|
4444
4465
|
/** Assistant messages */
|
|
4445
4466
|
assistantMessages: 'div[data-testid="assistant-message"]',
|
|
4446
|
-
/** Chat input (ProseMirror contenteditable) */
|
|
4447
|
-
input: '
|
|
4467
|
+
/** Chat input (ProseMirror contenteditable) - NOT a textarea */
|
|
4468
|
+
input: '[aria-label="Enter your turn"]',
|
|
4469
|
+
/** Chat input fallback */
|
|
4470
|
+
inputFallback: 'div.tiptap.ProseMirror[contenteditable="true"]',
|
|
4471
|
+
/** Toggle menu button (+) */
|
|
4472
|
+
toggleMenuButton: 'button[aria-label="Toggle menu"]',
|
|
4448
4473
|
/** Send button */
|
|
4449
|
-
sendButton: 'button[aria-label
|
|
4474
|
+
sendButton: 'form.w-full button[aria-label="Submit"]',
|
|
4450
4475
|
/** Stop button (during generation) */
|
|
4451
4476
|
stopButton: 'button:has-text("Stop")'
|
|
4452
4477
|
},
|
|
4453
4478
|
// ============================================================================
|
|
4454
|
-
// Model Selector
|
|
4479
|
+
// Model Selector - Verified 2026-02-23
|
|
4455
4480
|
// ============================================================================
|
|
4456
4481
|
model: {
|
|
4457
|
-
/** Model selector dropdown */
|
|
4458
|
-
|
|
4459
|
-
/** Model
|
|
4460
|
-
|
|
4482
|
+
/** Model selector dropdown (sidebar) */
|
|
4483
|
+
sidebarSelector: 'form:not(.w-full) [data-testid="model-selector-dropdown"]',
|
|
4484
|
+
/** Model selector dropdown (main chat) */
|
|
4485
|
+
chatSelector: 'form.w-full [data-testid="model-selector-dropdown"]',
|
|
4486
|
+
/** Model options in dropdown (role="menuitem") */
|
|
4487
|
+
options: '[role="menuitem"]',
|
|
4461
4488
|
/** Specific model option */
|
|
4462
|
-
option: (modelName) => `
|
|
4489
|
+
option: (modelName) => `[role="menuitem"]:has-text("${modelName}")`,
|
|
4490
|
+
/** Quick model selectors */
|
|
4491
|
+
opus: '[role="menuitem"]:has-text("Opus")',
|
|
4492
|
+
sonnet: '[role="menuitem"]:has-text("Sonnet")',
|
|
4493
|
+
haiku: '[role="menuitem"]:has-text("Haiku")'
|
|
4463
4494
|
},
|
|
4464
4495
|
// ============================================================================
|
|
4465
|
-
// Mode Toggle
|
|
4496
|
+
// Mode Toggle - Verified 2026-02-23
|
|
4497
|
+
// Text alternates between "Auto accept edits" and "Plan mode"
|
|
4466
4498
|
// ============================================================================
|
|
4467
4499
|
mode: {
|
|
4468
|
-
/** Mode toggle button - simple
|
|
4469
|
-
|
|
4470
|
-
/**
|
|
4471
|
-
|
|
4500
|
+
/** Mode toggle button in sidebar - simple toggle, not dropdown */
|
|
4501
|
+
sidebarToggle: 'form:not(.w-full) button:has-text("Auto accept edits"), form:not(.w-full) button:has-text("Plan mode")',
|
|
4502
|
+
/** Mode toggle button in main chat */
|
|
4503
|
+
chatToggle: 'form.w-full button:has-text("Auto accept edits"), form.w-full button:has-text("Plan mode")'
|
|
4472
4504
|
},
|
|
4473
4505
|
// ============================================================================
|
|
4474
|
-
// Repository Connection
|
|
4506
|
+
// Repository Connection - Verified 2026-02-23
|
|
4475
4507
|
// ============================================================================
|
|
4476
4508
|
repository: {
|
|
4477
|
-
/**
|
|
4478
|
-
|
|
4479
|
-
/** Repo
|
|
4480
|
-
repoName:
|
|
4481
|
-
/**
|
|
4482
|
-
|
|
4509
|
+
/** Repo button in sidebar (shows current repo name) */
|
|
4510
|
+
repoButton: "form:not(.w-full) button.flex.items-center.gap-1.min-w-0.rounded",
|
|
4511
|
+
/** Repo button by name */
|
|
4512
|
+
repoButtonByName: (repoName) => `form:not(.w-full) button:has-text("${repoName}")`,
|
|
4513
|
+
/** Add repository button (hidden until hover) */
|
|
4514
|
+
addRepoButton: 'button[aria-label="Add repository"]',
|
|
4515
|
+
/** Select repository dropdown */
|
|
4516
|
+
selectRepoDropdown: 'form:not(.w-full) button[aria-haspopup="menu"]:has-text("Select repository")'
|
|
4483
4517
|
},
|
|
4484
4518
|
// ============================================================================
|
|
4485
4519
|
// Session Archive
|
|
@@ -4515,17 +4549,6 @@ var ClaudeSelectors = {
|
|
|
4515
4549
|
listboxOptions: 'div[role="option"]'
|
|
4516
4550
|
}
|
|
4517
4551
|
};
|
|
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
4552
|
|
|
4530
4553
|
// apps/cli/src/commands/import.ts
|
|
4531
4554
|
var fs3 = __toESM(require("fs"));
|
|
@@ -4587,7 +4610,7 @@ Found ${sessions.length} sessions:
|
|
|
4587
4610
|
}
|
|
4588
4611
|
for (const [repoName, repoSessions] of sessionsByRepo) {
|
|
4589
4612
|
const matchingProject = existingProjects.find(
|
|
4590
|
-
(p) => p.githubRepo?.
|
|
4613
|
+
(p) => p.githubRepo?.endsWith(`/${repoName}`)
|
|
4591
4614
|
);
|
|
4592
4615
|
if (matchingProject) {
|
|
4593
4616
|
console.log(`\u{1F4C1} ${repoName} \u2192 Project: ${matchingProject.name} (${matchingProject.id.slice(0, 8)})`);
|
|
@@ -4626,12 +4649,52 @@ Summary:`);
|
|
|
4626
4649
|
console.log("\u{1F4A1} Run without --dry-run to create tasks in FlightDesk");
|
|
4627
4650
|
return;
|
|
4628
4651
|
}
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4652
|
+
const importableSessions = [];
|
|
4653
|
+
for (const [repoName, repoSessions] of sessionsByRepo) {
|
|
4654
|
+
const matchingProject = existingProjects.find(
|
|
4655
|
+
(p) => p.githubRepo?.endsWith(`/${repoName}`)
|
|
4656
|
+
);
|
|
4657
|
+
if (matchingProject) {
|
|
4658
|
+
for (const session of repoSessions) {
|
|
4659
|
+
importableSessions.push({ session, project: matchingProject });
|
|
4660
|
+
}
|
|
4661
|
+
}
|
|
4662
|
+
}
|
|
4663
|
+
if (importableSessions.length === 0) {
|
|
4664
|
+
console.log("No sessions can be imported (no matching projects).");
|
|
4665
|
+
console.log("Create projects in FlightDesk first for the repositories you want to track.");
|
|
4666
|
+
return;
|
|
4667
|
+
}
|
|
4668
|
+
console.log(`
|
|
4669
|
+
\u{1F680} Importing ${importableSessions.length} sessions...
|
|
4670
|
+
`);
|
|
4671
|
+
let created = 0;
|
|
4672
|
+
let failed = 0;
|
|
4673
|
+
for (const { session, project } of importableSessions) {
|
|
4674
|
+
try {
|
|
4675
|
+
const task2 = await api.createTask({
|
|
4676
|
+
projectId: project.id,
|
|
4677
|
+
title: session.title,
|
|
4678
|
+
description: `Imported from Claude Code session`
|
|
4679
|
+
});
|
|
4680
|
+
await api.updateTask(task2.id, {
|
|
4681
|
+
branchName: session.branchName,
|
|
4682
|
+
sessionViewUrl: session.url
|
|
4683
|
+
});
|
|
4684
|
+
console.log(` \u2705 ${session.title.slice(0, 50)}`);
|
|
4685
|
+
created++;
|
|
4686
|
+
} catch (error) {
|
|
4687
|
+
console.error(` \u274C ${session.title.slice(0, 50)}: ${error.message}`);
|
|
4688
|
+
failed++;
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4633
4691
|
console.log("");
|
|
4634
|
-
console.log("
|
|
4692
|
+
console.log("\u2500".repeat(60));
|
|
4693
|
+
console.log(`
|
|
4694
|
+
Import complete:`);
|
|
4695
|
+
console.log(` Created: ${created}`);
|
|
4696
|
+
console.log(` Failed: ${failed}`);
|
|
4697
|
+
console.log(` Skipped (no matching project): ${sessions.length - importableSessions.length}`);
|
|
4635
4698
|
}
|
|
4636
4699
|
async function scanClaudeSessions(options) {
|
|
4637
4700
|
if (!playwright2) {
|
|
@@ -4647,36 +4710,59 @@ async function scanClaudeSessions(options) {
|
|
|
4647
4710
|
});
|
|
4648
4711
|
try {
|
|
4649
4712
|
const page = await context.newPage();
|
|
4650
|
-
page.setDefaultTimeout(
|
|
4651
|
-
console.log("Navigating to Claude
|
|
4652
|
-
await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded" });
|
|
4653
|
-
|
|
4713
|
+
page.setDefaultTimeout(6e4);
|
|
4714
|
+
console.log("Navigating to Claude Code...");
|
|
4715
|
+
await page.goto("https://claude.ai/code", { waitUntil: "domcontentloaded", timeout: 6e4 });
|
|
4716
|
+
if (options.verbose) {
|
|
4717
|
+
console.log("Waiting for page to load...");
|
|
4718
|
+
}
|
|
4719
|
+
await page.waitForTimeout(5e3);
|
|
4654
4720
|
const url = page.url();
|
|
4721
|
+
if (options.verbose) {
|
|
4722
|
+
console.log("Current URL:", url);
|
|
4723
|
+
}
|
|
4655
4724
|
if (url.includes("/login") || url.includes("/oauth")) {
|
|
4656
4725
|
console.log("\n\u26A0\uFE0F Not logged into Claude. Run: flightdesk auth\n");
|
|
4657
4726
|
return [];
|
|
4658
4727
|
}
|
|
4659
4728
|
console.log("Logged in. Scanning sessions...");
|
|
4729
|
+
await page.waitForTimeout(3e3);
|
|
4660
4730
|
const sessions = [];
|
|
4661
|
-
const
|
|
4731
|
+
const sessionItems = await page.$$(ClaudeSelectors.sidebar.allSessions);
|
|
4662
4732
|
if (options.verbose) {
|
|
4663
|
-
console.log(`Found ${
|
|
4733
|
+
console.log(`Found ${sessionItems.length} session items in sidebar`);
|
|
4664
4734
|
}
|
|
4665
|
-
const limit = Math.min(
|
|
4735
|
+
const limit = Math.min(sessionItems.length, options.limit);
|
|
4666
4736
|
for (let i = 0; i < limit; i++) {
|
|
4667
|
-
const
|
|
4668
|
-
if (i >=
|
|
4669
|
-
const
|
|
4737
|
+
const items = await page.$$(ClaudeSelectors.sidebar.allSessions);
|
|
4738
|
+
if (i >= items.length) break;
|
|
4739
|
+
const item = items[i];
|
|
4670
4740
|
try {
|
|
4671
|
-
const
|
|
4672
|
-
const title = await
|
|
4673
|
-
|
|
4674
|
-
|
|
4741
|
+
const titleElement = await item.$("span.text-text-100");
|
|
4742
|
+
const title = titleElement ? (await titleElement.textContent())?.trim() || "Untitled" : "Untitled";
|
|
4743
|
+
let repoName;
|
|
4744
|
+
try {
|
|
4745
|
+
const repoElement = await item.$("span.text-text-500 span.truncate");
|
|
4746
|
+
if (repoElement) {
|
|
4747
|
+
const repoText = (await repoElement.textContent())?.trim();
|
|
4748
|
+
if (repoText && repoText.length > 0) {
|
|
4749
|
+
repoName = repoText;
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
} catch {
|
|
4753
|
+
}
|
|
4675
4754
|
if (options.verbose) {
|
|
4676
|
-
|
|
4755
|
+
const repoDisplay = repoName ? ` (${repoName})` : "";
|
|
4756
|
+
process.stdout.write(`\r Scanning ${i + 1}/${limit}: ${title.slice(0, 35)}${repoDisplay}...`);
|
|
4677
4757
|
}
|
|
4678
|
-
|
|
4679
|
-
|
|
4758
|
+
try {
|
|
4759
|
+
await page.keyboard.press("Escape");
|
|
4760
|
+
await page.waitForTimeout(300);
|
|
4761
|
+
} catch {
|
|
4762
|
+
}
|
|
4763
|
+
await item.click();
|
|
4764
|
+
await page.waitForTimeout(2e3);
|
|
4765
|
+
const sessionUrl = page.url();
|
|
4680
4766
|
let branchName;
|
|
4681
4767
|
try {
|
|
4682
4768
|
const branchElement = await page.$(ClaudeSelectors.branchBar.branchName);
|
|
@@ -4685,20 +4771,6 @@ async function scanClaudeSessions(options) {
|
|
|
4685
4771
|
}
|
|
4686
4772
|
} catch {
|
|
4687
4773
|
}
|
|
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
4774
|
const archived = !!await page.$(ClaudeSelectors.archive.indicator);
|
|
4703
4775
|
sessions.push({
|
|
4704
4776
|
url: sessionUrl,
|
|
@@ -4725,7 +4797,18 @@ async function scanClaudeSessions(options) {
|
|
|
4725
4797
|
|
|
4726
4798
|
// apps/cli/src/main.ts
|
|
4727
4799
|
var program2 = new Command();
|
|
4728
|
-
program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.1.
|
|
4800
|
+
program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.1.7").option("--dev", "Use local development API (localhost:3000)").option("--api <url>", "Use custom API URL");
|
|
4801
|
+
program2.hook("preAction", () => {
|
|
4802
|
+
const opts = program2.opts();
|
|
4803
|
+
if (opts.api) {
|
|
4804
|
+
setApiUrl(opts.api);
|
|
4805
|
+
console.log(`\u{1F527} Using custom API: ${opts.api}
|
|
4806
|
+
`);
|
|
4807
|
+
} else if (opts.dev) {
|
|
4808
|
+
setDevMode(true);
|
|
4809
|
+
console.log("\u{1F527} Development mode: using http://localhost:3000\n");
|
|
4810
|
+
}
|
|
4811
|
+
});
|
|
4729
4812
|
program2.command("init").description("Configure FlightDesk CLI with your API credentials").action(initCommand);
|
|
4730
4813
|
program2.command("auth").description("Log in to Claude for session monitoring").action(authCommand);
|
|
4731
4814
|
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);
|