flightdesk 0.2.5 → 0.3.0
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 +110 -80
- package/main.js.map +3 -3
- package/package.json +1 -1
package/main.js
CHANGED
|
@@ -3216,6 +3216,8 @@ var FlightDeskAPI = class _FlightDeskAPI {
|
|
|
3216
3216
|
status
|
|
3217
3217
|
branchName
|
|
3218
3218
|
prUrl
|
|
3219
|
+
sessionViewUrl
|
|
3220
|
+
sessionTeleportId
|
|
3219
3221
|
}
|
|
3220
3222
|
}
|
|
3221
3223
|
`;
|
|
@@ -3286,7 +3288,10 @@ var FlightDeskAPI = class _FlightDeskAPI {
|
|
|
3286
3288
|
input.projectId = options.projectId;
|
|
3287
3289
|
}
|
|
3288
3290
|
if (options?.status) {
|
|
3289
|
-
input.status = [options.status];
|
|
3291
|
+
input.status = Array.isArray(options.status) ? options.status : [options.status];
|
|
3292
|
+
}
|
|
3293
|
+
if (options?.limit) {
|
|
3294
|
+
input.limit = options.limit;
|
|
3290
3295
|
}
|
|
3291
3296
|
const result = await this.graphql(query, { input });
|
|
3292
3297
|
return result.userTasks;
|
|
@@ -3389,6 +3394,11 @@ var FlightDeskAPI = class _FlightDeskAPI {
|
|
|
3389
3394
|
createdAt
|
|
3390
3395
|
updatedAt
|
|
3391
3396
|
sshConnectionString
|
|
3397
|
+
processUrls {
|
|
3398
|
+
name
|
|
3399
|
+
url
|
|
3400
|
+
primary
|
|
3401
|
+
}
|
|
3392
3402
|
}
|
|
3393
3403
|
}
|
|
3394
3404
|
`;
|
|
@@ -3864,44 +3874,63 @@ function waitForEnter() {
|
|
|
3864
3874
|
});
|
|
3865
3875
|
});
|
|
3866
3876
|
}
|
|
3877
|
+
async function navigateAndPrepare(page, sessionUrl, timeout, debug) {
|
|
3878
|
+
if (debug) console.log(` [DEBUG] Navigating to: ${sessionUrl}`);
|
|
3879
|
+
await page.goto(sessionUrl, { waitUntil: "domcontentloaded", timeout });
|
|
3880
|
+
const dismissBtn = await page.$(`button:has-text("Don't ask me again"), button:has-text("Got it")`);
|
|
3881
|
+
if (dismissBtn) {
|
|
3882
|
+
if (debug) console.log(" [DEBUG] Dismissing notification modal");
|
|
3883
|
+
await dismissBtn.click();
|
|
3884
|
+
await page.waitForTimeout(500);
|
|
3885
|
+
}
|
|
3886
|
+
const matchedElement = await page.waitForSelector('[data-testid="conversation-turn"], .cursor-pointer.bg-bg-300, button:has-text("Create PR")', {
|
|
3887
|
+
timeout: 1e4
|
|
3888
|
+
}).catch(() => null);
|
|
3889
|
+
if (debug) {
|
|
3890
|
+
if (matchedElement) {
|
|
3891
|
+
const tagName = await matchedElement.evaluate((el) => `${el.tagName}.${el.className}`);
|
|
3892
|
+
console.log(` [DEBUG] waitForSelector matched: ${tagName}`);
|
|
3893
|
+
} else {
|
|
3894
|
+
console.log(" [DEBUG] waitForSelector timed out - no expected elements found");
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
async function captureDebugInfo(page) {
|
|
3899
|
+
console.log(` [DEBUG] Current URL after navigation: ${page.url()}`);
|
|
3900
|
+
const screenshotDir = path2.join(os2.homedir(), ".flightdesk", "debug-screenshots");
|
|
3901
|
+
if (!fs2.existsSync(screenshotDir)) fs2.mkdirSync(screenshotDir, { recursive: true });
|
|
3902
|
+
const screenshotPath = path2.join(screenshotDir, `session-${Date.now()}.png`);
|
|
3903
|
+
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
3904
|
+
console.log(` [DEBUG] Screenshot saved: ${screenshotPath}`);
|
|
3905
|
+
}
|
|
3906
|
+
async function autoCreatePr(page, createPrButton) {
|
|
3907
|
+
console.log(' Clicking "Create PR" button...');
|
|
3908
|
+
await createPrButton.click();
|
|
3909
|
+
await page.waitForTimeout(5e3);
|
|
3910
|
+
return await extractPrUrl(page) ?? void 0;
|
|
3911
|
+
}
|
|
3867
3912
|
async function scrapeSession(page, sessionUrl, options = {}) {
|
|
3868
3913
|
const { timeout = 3e4, autoPr = false } = options;
|
|
3869
3914
|
try {
|
|
3870
3915
|
page.setDefaultTimeout(timeout);
|
|
3871
|
-
|
|
3872
|
-
await page
|
|
3873
|
-
|
|
3874
|
-
}).catch(() => {
|
|
3875
|
-
});
|
|
3876
|
-
const result = { status: "active" };
|
|
3916
|
+
const debug = process.env.FLIGHTDESK_DEBUG === "1";
|
|
3917
|
+
await navigateAndPrepare(page, sessionUrl, timeout, debug);
|
|
3918
|
+
if (debug) await captureDebugInfo(page);
|
|
3877
3919
|
const archiveIndicator = await page.$("text=This session has been archived");
|
|
3878
|
-
if (archiveIndicator) {
|
|
3879
|
-
return { status: "archived" };
|
|
3880
|
-
}
|
|
3920
|
+
if (archiveIndicator) return { status: "archived" };
|
|
3881
3921
|
const url = page.url();
|
|
3882
3922
|
if (url.includes("/login") || url.includes("/oauth")) {
|
|
3883
|
-
return {
|
|
3884
|
-
status: "error",
|
|
3885
|
-
error: "Not logged in to Claude. Run: flightdesk auth"
|
|
3886
|
-
};
|
|
3923
|
+
return { status: "error", error: "Not logged in to Claude. Run: flightdesk auth" };
|
|
3887
3924
|
}
|
|
3888
|
-
const
|
|
3889
|
-
result.isActive =
|
|
3925
|
+
const result = { status: "active" };
|
|
3926
|
+
result.isActive = await detectActiveSpinner(page, debug);
|
|
3890
3927
|
const branchName = await extractBranchName(page);
|
|
3891
|
-
if (branchName)
|
|
3892
|
-
result.branchName = branchName;
|
|
3893
|
-
}
|
|
3928
|
+
if (branchName) result.branchName = branchName;
|
|
3894
3929
|
const createPrButton = await page.$('button:has-text("Create PR"):not([aria-haspopup])');
|
|
3895
3930
|
const viewPrButton = await page.$('button:has-text("View PR")');
|
|
3896
3931
|
result.hasPrButton = !!createPrButton && !viewPrButton;
|
|
3897
3932
|
if (autoPr && createPrButton && !viewPrButton) {
|
|
3898
|
-
|
|
3899
|
-
await createPrButton.click();
|
|
3900
|
-
await page.waitForTimeout(5e3);
|
|
3901
|
-
const prUrl = await extractPrUrl(page);
|
|
3902
|
-
if (prUrl) {
|
|
3903
|
-
result.prUrl = prUrl;
|
|
3904
|
-
}
|
|
3933
|
+
result.prUrl = await autoCreatePr(page, createPrButton);
|
|
3905
3934
|
}
|
|
3906
3935
|
return result;
|
|
3907
3936
|
} catch (error) {
|
|
@@ -3994,58 +4023,27 @@ async function extractPrUrl(page) {
|
|
|
3994
4023
|
}
|
|
3995
4024
|
return null;
|
|
3996
4025
|
}
|
|
3997
|
-
async function detectActiveSpinner(page) {
|
|
4026
|
+
async function detectActiveSpinner(page, debug = false) {
|
|
3998
4027
|
try {
|
|
3999
|
-
const
|
|
4000
|
-
if (
|
|
4001
|
-
const
|
|
4002
|
-
|
|
4003
|
-
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
|
|
4004
|
-
return false;
|
|
4005
|
-
}
|
|
4006
|
-
if (style.animationName && style.animationName !== "none") {
|
|
4007
|
-
return true;
|
|
4008
|
-
}
|
|
4009
|
-
if (style.animationPlayState === "paused") {
|
|
4010
|
-
return false;
|
|
4011
|
-
}
|
|
4012
|
-
const rect = el.getBoundingClientRect();
|
|
4013
|
-
return rect.width > 0 && rect.height > 0;
|
|
4014
|
-
});
|
|
4015
|
-
if (isVisibleAndAnimating) {
|
|
4016
|
-
return true;
|
|
4017
|
-
}
|
|
4028
|
+
const selectedRowSpinner = await page.$(".cursor-pointer.bg-bg-300 .code-spinner-animate");
|
|
4029
|
+
if (debug) {
|
|
4030
|
+
const globalSpinner = await page.$(".code-spinner-animate");
|
|
4031
|
+
console.log(` [DEBUG] .code-spinner-animate global: ${!!globalSpinner}, in selected row: ${!!selectedRowSpinner}`);
|
|
4018
4032
|
}
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
current = current.parentElement;
|
|
4029
|
-
continue;
|
|
4030
|
-
}
|
|
4031
|
-
if (style2.animationPlayState === "paused") {
|
|
4032
|
-
current = current.parentElement;
|
|
4033
|
-
continue;
|
|
4034
|
-
}
|
|
4035
|
-
return style2.animationName !== "none";
|
|
4036
|
-
}
|
|
4037
|
-
const style = globalThis.getComputedStyle(current);
|
|
4038
|
-
if (style.animationName && style.animationName !== "none" && style.animationPlayState !== "paused") {
|
|
4039
|
-
return true;
|
|
4040
|
-
}
|
|
4041
|
-
current = current.parentElement;
|
|
4042
|
-
}
|
|
4043
|
-
return false;
|
|
4044
|
-
});
|
|
4045
|
-
return hasAnimation;
|
|
4033
|
+
if (selectedRowSpinner) {
|
|
4034
|
+
if (debug) console.log(" [DEBUG] Selected sidebar row has spinner: ACTIVE");
|
|
4035
|
+
return true;
|
|
4036
|
+
}
|
|
4037
|
+
const stopButton = await page.$('button:has-text("Stop")');
|
|
4038
|
+
if (debug) console.log(` [DEBUG] Stop button found: ${!!stopButton}`);
|
|
4039
|
+
if (stopButton) {
|
|
4040
|
+
if (debug) console.log(" [DEBUG] Stop button detected: ACTIVE");
|
|
4041
|
+
return true;
|
|
4046
4042
|
}
|
|
4043
|
+
if (debug) console.log(" [DEBUG] No activity indicators: IDLE");
|
|
4047
4044
|
return false;
|
|
4048
|
-
} catch {
|
|
4045
|
+
} catch (error) {
|
|
4046
|
+
if (debug) console.log(` [DEBUG] Spinner detection error: ${error}`);
|
|
4049
4047
|
return false;
|
|
4050
4048
|
}
|
|
4051
4049
|
}
|
|
@@ -4415,7 +4413,11 @@ async function watchCommand(options) {
|
|
|
4415
4413
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
4416
4414
|
console.log(`[${timestamp}] Checking tasks...`);
|
|
4417
4415
|
try {
|
|
4418
|
-
const
|
|
4416
|
+
const ACTIVE_STATUSES = ["PENDING", "DISPATCHED", "IN_PROGRESS", "BRANCH_CREATED"];
|
|
4417
|
+
const tasks = await api.listTasks({
|
|
4418
|
+
status: ACTIVE_STATUSES,
|
|
4419
|
+
limit: 200
|
|
4420
|
+
});
|
|
4419
4421
|
const activeTasks = tasks.filter(
|
|
4420
4422
|
(t) => ["DISPATCHED", "IN_PROGRESS", "BRANCH_CREATED"].includes(t.status) || t.status === "PENDING" && t.sessionViewUrl
|
|
4421
4423
|
);
|
|
@@ -4663,15 +4665,34 @@ ${task2.description}`);
|
|
|
4663
4665
|
Created: ${new Date(task2.createdAt).toLocaleString()}`);
|
|
4664
4666
|
console.log(`Updated: ${new Date(task2.updatedAt).toLocaleString()}`);
|
|
4665
4667
|
}
|
|
4668
|
+
function parseSessionId(value) {
|
|
4669
|
+
const urlPrefix = "https://claude.ai/code/";
|
|
4670
|
+
if (value.startsWith(urlPrefix)) {
|
|
4671
|
+
return value.slice(urlPrefix.length);
|
|
4672
|
+
}
|
|
4673
|
+
return value;
|
|
4674
|
+
}
|
|
4666
4675
|
async function handleUpdate(api, options) {
|
|
4667
4676
|
const input = {};
|
|
4668
4677
|
if (options.status) input.status = options.status;
|
|
4669
4678
|
if (options.branch) input.branchName = options.branch;
|
|
4670
4679
|
if (options.prUrl) input.prUrl = options.prUrl;
|
|
4680
|
+
if (options.session) {
|
|
4681
|
+
const sessionId = parseSessionId(options.session);
|
|
4682
|
+
input.sessionViewUrl = `https://claude.ai/code/${sessionId}`;
|
|
4683
|
+
input.sessionTeleportId = sessionId;
|
|
4684
|
+
}
|
|
4671
4685
|
if (Object.keys(input).length === 0) {
|
|
4672
|
-
console.error("No updates specified. Use --status, --branch,
|
|
4686
|
+
console.error("No updates specified. Use --status, --branch, --pr-url, or --session");
|
|
4673
4687
|
process.exit(1);
|
|
4674
4688
|
}
|
|
4689
|
+
if (options.branch && !options.status) {
|
|
4690
|
+
const current = await api.getTask(options.taskId);
|
|
4691
|
+
if (current?.status === "DISPATCHED") {
|
|
4692
|
+
input.status = "IN_PROGRESS";
|
|
4693
|
+
console.log(` Auto-transitioning DISPATCHED \u2192 IN_PROGRESS`);
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4675
4696
|
console.log(`Updating task ${options.taskId}...`);
|
|
4676
4697
|
const task2 = await api.updateTask(options.taskId, input);
|
|
4677
4698
|
console.log(`
|
|
@@ -4679,6 +4700,7 @@ async function handleUpdate(api, options) {
|
|
|
4679
4700
|
console.log(` Status: ${task2.status}`);
|
|
4680
4701
|
if (task2.branchName) console.log(` Branch: ${task2.branchName}`);
|
|
4681
4702
|
if (task2.prUrl) console.log(` PR: ${task2.prUrl}`);
|
|
4703
|
+
if (task2.sessionViewUrl) console.log(` Session: ${task2.sessionViewUrl}`);
|
|
4682
4704
|
}
|
|
4683
4705
|
function getStatusEmoji2(status) {
|
|
4684
4706
|
switch (status) {
|
|
@@ -5466,7 +5488,15 @@ async function handleStatus2(api, options) {
|
|
|
5466
5488
|
console.log("\u2500".repeat(50));
|
|
5467
5489
|
console.log(`${emoji} Status: ${instance.status}`);
|
|
5468
5490
|
console.log(` ID: ${instance.id.substring(0, 8)}`);
|
|
5469
|
-
|
|
5491
|
+
if (instance.processUrls && instance.processUrls.length > 0) {
|
|
5492
|
+
console.log(" URLs:");
|
|
5493
|
+
for (const pu of instance.processUrls) {
|
|
5494
|
+
const label = pu.primary ? `${pu.name} (primary)` : pu.name;
|
|
5495
|
+
console.log(` ${label}: ${pu.url}`);
|
|
5496
|
+
}
|
|
5497
|
+
} else {
|
|
5498
|
+
console.log(` Preview URL: ${instance.previewUrl}`);
|
|
5499
|
+
}
|
|
5470
5500
|
console.log(` SSH: ${instance.sshConnectionString}`);
|
|
5471
5501
|
console.log(` Container: ${instance.containerId.substring(0, 12)}`);
|
|
5472
5502
|
if (instance.lastActivityAt) {
|
|
@@ -5541,7 +5571,7 @@ async function handleMount(api, options) {
|
|
|
5541
5571
|
console.error("");
|
|
5542
5572
|
console.error("Install it with:");
|
|
5543
5573
|
if (process.platform === "darwin") {
|
|
5544
|
-
console.error(" brew install macfuse sshfs");
|
|
5574
|
+
console.error(" brew install macfuse gromgit/fuse/sshfs-mac");
|
|
5545
5575
|
} else if (process.platform === "linux") {
|
|
5546
5576
|
console.error(" sudo apt install sshfs");
|
|
5547
5577
|
} else {
|
|
@@ -5658,7 +5688,7 @@ async function handleTeardown(api, options) {
|
|
|
5658
5688
|
|
|
5659
5689
|
// apps/cli/src/main.ts
|
|
5660
5690
|
var program2 = new Command();
|
|
5661
|
-
program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.
|
|
5691
|
+
program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.3.0").option("--dev", "Use local development API (localhost:3000)").option("--api <url>", "Use custom API URL");
|
|
5662
5692
|
program2.hook("preAction", () => {
|
|
5663
5693
|
const opts = program2.opts();
|
|
5664
5694
|
if (opts.api) {
|
|
@@ -5679,7 +5709,7 @@ var task = program2.command("task").description("Task management commands");
|
|
|
5679
5709
|
task.command("create").description("Create a new task").requiredOption("-p, --project <id>", "Project ID").requiredOption("-t, --title <title>", "Task title").option("-d, --description <description>", "Task description").action((options) => taskCommand("create", options));
|
|
5680
5710
|
task.command("list").description("List tasks").option("-p, --project <id>", "Filter by project ID").option("--status <status>", "Filter by status").action((options) => taskCommand("list", options));
|
|
5681
5711
|
task.command("status <task-id>").description("Get task status").action((taskId) => taskCommand("status", { taskId }));
|
|
5682
|
-
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 }));
|
|
5712
|
+
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").option("--session <session>", "Claude Code session (URL or session ID)").action((taskId, options) => taskCommand("update", { taskId, ...options }));
|
|
5683
5713
|
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);
|
|
5684
5714
|
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);
|
|
5685
5715
|
program2.command("status").description("Show status of all active tasks").option("-p, --project <id>", "Filter by project").action(statusCommand);
|