flightdesk 0.2.1 → 0.2.3
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 +278 -64
- package/main.js.map +2 -2
- package/package.json +1 -1
package/main.js
CHANGED
|
@@ -3579,6 +3579,86 @@ var fs2 = __toESM(require("node:fs"));
|
|
|
3579
3579
|
var readline2 = __toESM(require("node:readline"));
|
|
3580
3580
|
var playwright = null;
|
|
3581
3581
|
var USER_DATA_DIR = path2.join(os2.homedir(), ".flightdesk", "chromium-profile");
|
|
3582
|
+
var STORAGE_STATE_FILE = path2.join(os2.homedir(), ".flightdesk", "auth-state.json");
|
|
3583
|
+
var PersistentBrowser = class {
|
|
3584
|
+
constructor(headless = true) {
|
|
3585
|
+
this.browser = null;
|
|
3586
|
+
this.context = null;
|
|
3587
|
+
this.page = null;
|
|
3588
|
+
this.headless = headless;
|
|
3589
|
+
}
|
|
3590
|
+
/**
|
|
3591
|
+
* Initialize the browser context (if not already initialized)
|
|
3592
|
+
*/
|
|
3593
|
+
async init() {
|
|
3594
|
+
if (this.context) return;
|
|
3595
|
+
if (!await isPlaywrightAvailable()) {
|
|
3596
|
+
throw new Error("Playwright not available");
|
|
3597
|
+
}
|
|
3598
|
+
ensureUserDataDir();
|
|
3599
|
+
this.browser = await playwright.chromium.launch({
|
|
3600
|
+
headless: this.headless
|
|
3601
|
+
});
|
|
3602
|
+
const hasAuthState = fs2.existsSync(STORAGE_STATE_FILE);
|
|
3603
|
+
const contextOptions = {
|
|
3604
|
+
viewport: { width: 1280, height: 720 },
|
|
3605
|
+
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
3606
|
+
};
|
|
3607
|
+
if (hasAuthState) {
|
|
3608
|
+
contextOptions.storageState = STORAGE_STATE_FILE;
|
|
3609
|
+
}
|
|
3610
|
+
this.context = await this.browser.newContext(contextOptions);
|
|
3611
|
+
this.page = await this.context.newPage();
|
|
3612
|
+
this.page.setDefaultTimeout(3e4);
|
|
3613
|
+
}
|
|
3614
|
+
/**
|
|
3615
|
+
* Check if user is logged into Claude
|
|
3616
|
+
*/
|
|
3617
|
+
async checkAuth() {
|
|
3618
|
+
await this.init();
|
|
3619
|
+
try {
|
|
3620
|
+
await this.page.goto("https://claude.ai/", { waitUntil: "networkidle", timeout: 3e4 });
|
|
3621
|
+
await this.page.waitForTimeout(2e3);
|
|
3622
|
+
const url = this.page.url();
|
|
3623
|
+
console.log(" Final URL:", url);
|
|
3624
|
+
return !url.includes("/login") && !url.includes("/oauth") && !url.includes("accounts.google") && url.includes("claude.ai");
|
|
3625
|
+
} catch (error) {
|
|
3626
|
+
console.error("Auth check failed:", error.message);
|
|
3627
|
+
return false;
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
/**
|
|
3631
|
+
* Monitor a Claude Code session URL
|
|
3632
|
+
* Uses shared scrapeSession helper for consistent behavior with one-shot mode.
|
|
3633
|
+
*/
|
|
3634
|
+
async monitorSession(sessionUrl, options = {}) {
|
|
3635
|
+
await this.init();
|
|
3636
|
+
if (!this.page) {
|
|
3637
|
+
return { status: "error", error: "Browser page not initialized" };
|
|
3638
|
+
}
|
|
3639
|
+
return scrapeSession(this.page, sessionUrl, options);
|
|
3640
|
+
}
|
|
3641
|
+
/**
|
|
3642
|
+
* Close the browser context and browser
|
|
3643
|
+
*/
|
|
3644
|
+
async close() {
|
|
3645
|
+
if (this.context) {
|
|
3646
|
+
try {
|
|
3647
|
+
await this.context.close();
|
|
3648
|
+
} catch {
|
|
3649
|
+
}
|
|
3650
|
+
this.context = null;
|
|
3651
|
+
this.page = null;
|
|
3652
|
+
}
|
|
3653
|
+
if (this.browser) {
|
|
3654
|
+
try {
|
|
3655
|
+
await this.browser.close();
|
|
3656
|
+
} catch {
|
|
3657
|
+
}
|
|
3658
|
+
this.browser = null;
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3661
|
+
};
|
|
3582
3662
|
async function isPlaywrightAvailable() {
|
|
3583
3663
|
try {
|
|
3584
3664
|
playwright = await import("playwright");
|
|
@@ -3589,7 +3669,23 @@ async function isPlaywrightAvailable() {
|
|
|
3589
3669
|
}
|
|
3590
3670
|
function ensureUserDataDir() {
|
|
3591
3671
|
if (!fs2.existsSync(USER_DATA_DIR)) {
|
|
3592
|
-
fs2.mkdirSync(USER_DATA_DIR, { recursive: true });
|
|
3672
|
+
fs2.mkdirSync(USER_DATA_DIR, { recursive: true, mode: 448 });
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
async function ensureStorageDir() {
|
|
3676
|
+
const storageDir = path2.dirname(STORAGE_STATE_FILE);
|
|
3677
|
+
try {
|
|
3678
|
+
await fs2.promises.mkdir(storageDir, { recursive: true, mode: 448 });
|
|
3679
|
+
} catch (err) {
|
|
3680
|
+
if (err?.code !== "EEXIST") {
|
|
3681
|
+
throw err;
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
async function setStorageFilePermissions() {
|
|
3686
|
+
try {
|
|
3687
|
+
await fs2.promises.chmod(STORAGE_STATE_FILE, 384);
|
|
3688
|
+
} catch {
|
|
3593
3689
|
}
|
|
3594
3690
|
}
|
|
3595
3691
|
async function launchBrowser(headless) {
|
|
@@ -3597,24 +3693,28 @@ async function launchBrowser(headless) {
|
|
|
3597
3693
|
throw new Error("Playwright not available");
|
|
3598
3694
|
}
|
|
3599
3695
|
ensureUserDataDir();
|
|
3600
|
-
const
|
|
3601
|
-
|
|
3696
|
+
const browser = await playwright.chromium.launch({ headless });
|
|
3697
|
+
const hasAuthState = fs2.existsSync(STORAGE_STATE_FILE);
|
|
3698
|
+
const contextOptions = {
|
|
3602
3699
|
viewport: { width: 1280, height: 720 },
|
|
3603
3700
|
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
3604
|
-
}
|
|
3605
|
-
|
|
3701
|
+
};
|
|
3702
|
+
if (hasAuthState) {
|
|
3703
|
+
contextOptions.storageState = STORAGE_STATE_FILE;
|
|
3704
|
+
}
|
|
3705
|
+
const context = await browser.newContext(contextOptions);
|
|
3606
3706
|
return { browser, context };
|
|
3607
3707
|
}
|
|
3608
3708
|
async function checkAuth() {
|
|
3609
3709
|
if (!await isPlaywrightAvailable()) {
|
|
3610
3710
|
throw new Error("Playwright not installed");
|
|
3611
3711
|
}
|
|
3612
|
-
const { context } = await launchBrowser(true);
|
|
3712
|
+
const { browser, context } = await launchBrowser(true);
|
|
3613
3713
|
try {
|
|
3614
3714
|
const page = await context.newPage();
|
|
3615
3715
|
page.setDefaultTimeout(3e4);
|
|
3616
|
-
await page.goto("https://claude.ai/", { waitUntil: "
|
|
3617
|
-
await page.waitForTimeout(
|
|
3716
|
+
await page.goto("https://claude.ai/", { waitUntil: "networkidle", timeout: 3e4 });
|
|
3717
|
+
await page.waitForTimeout(2e3);
|
|
3618
3718
|
const url = page.url();
|
|
3619
3719
|
console.log(" Final URL:", url);
|
|
3620
3720
|
const isLoggedIn = !url.includes("/login") && !url.includes("/oauth") && !url.includes("accounts.google") && url.includes("claude.ai");
|
|
@@ -3624,6 +3724,7 @@ async function checkAuth() {
|
|
|
3624
3724
|
return false;
|
|
3625
3725
|
} finally {
|
|
3626
3726
|
await context.close();
|
|
3727
|
+
await browser.close();
|
|
3627
3728
|
}
|
|
3628
3729
|
}
|
|
3629
3730
|
async function openForLogin() {
|
|
@@ -3643,6 +3744,13 @@ async function openForLogin() {
|
|
|
3643
3744
|
const url = page.url();
|
|
3644
3745
|
console.log(" Final URL:", url);
|
|
3645
3746
|
const isLoggedIn = !url.includes("/login") && !url.includes("/oauth") && !url.includes("accounts.google") && url.includes("claude.ai");
|
|
3747
|
+
if (isLoggedIn) {
|
|
3748
|
+
await ensureStorageDir();
|
|
3749
|
+
console.log("Saving session state...");
|
|
3750
|
+
await context.storageState({ path: STORAGE_STATE_FILE });
|
|
3751
|
+
await setStorageFilePermissions();
|
|
3752
|
+
console.log(` Saved to: ${STORAGE_STATE_FILE}`);
|
|
3753
|
+
}
|
|
3646
3754
|
console.log("Closing browser...");
|
|
3647
3755
|
return isLoggedIn;
|
|
3648
3756
|
} finally {
|
|
@@ -3664,17 +3772,15 @@ function waitForEnter() {
|
|
|
3664
3772
|
});
|
|
3665
3773
|
});
|
|
3666
3774
|
}
|
|
3667
|
-
async function
|
|
3668
|
-
|
|
3669
|
-
return { status: "error", error: "Playwright not installed" };
|
|
3670
|
-
}
|
|
3671
|
-
const { headless = true, timeout = 3e4, autoPr = false } = options;
|
|
3672
|
-
const { context } = await launchBrowser(headless);
|
|
3775
|
+
async function scrapeSession(page, sessionUrl, options = {}) {
|
|
3776
|
+
const { timeout = 3e4, autoPr = false } = options;
|
|
3673
3777
|
try {
|
|
3674
|
-
const page = await context.newPage();
|
|
3675
3778
|
page.setDefaultTimeout(timeout);
|
|
3676
|
-
await page.goto(sessionUrl, { waitUntil: "
|
|
3677
|
-
await page.
|
|
3779
|
+
await page.goto(sessionUrl, { waitUntil: "networkidle" });
|
|
3780
|
+
await page.waitForSelector('[data-testid="conversation-turn"], .code-spinner-animate, button:has-text("Create PR")', {
|
|
3781
|
+
timeout: 1e4
|
|
3782
|
+
}).catch(() => {
|
|
3783
|
+
});
|
|
3678
3784
|
const result = { status: "active" };
|
|
3679
3785
|
const archiveIndicator = await page.$("text=This session has been archived");
|
|
3680
3786
|
if (archiveIndicator) {
|
|
@@ -3687,15 +3793,18 @@ async function monitorSession(sessionUrl, options = {}) {
|
|
|
3687
3793
|
error: "Not logged in to Claude. Run: flightdesk auth"
|
|
3688
3794
|
};
|
|
3689
3795
|
}
|
|
3796
|
+
const isActive = await detectActiveSpinner(page);
|
|
3797
|
+
result.isActive = isActive;
|
|
3690
3798
|
const branchName = await extractBranchName(page);
|
|
3691
3799
|
if (branchName) {
|
|
3692
3800
|
result.branchName = branchName;
|
|
3693
3801
|
}
|
|
3694
|
-
const
|
|
3695
|
-
|
|
3696
|
-
|
|
3802
|
+
const createPrButton = await page.$('button:has-text("Create PR"):not([aria-haspopup])');
|
|
3803
|
+
const viewPrButton = await page.$('button:has-text("View PR")');
|
|
3804
|
+
result.hasPrButton = !!createPrButton && !viewPrButton;
|
|
3805
|
+
if (autoPr && createPrButton && !viewPrButton) {
|
|
3697
3806
|
console.log(' Clicking "Create PR" button...');
|
|
3698
|
-
await
|
|
3807
|
+
await createPrButton.click();
|
|
3699
3808
|
await page.waitForTimeout(5e3);
|
|
3700
3809
|
const prUrl = await extractPrUrl(page);
|
|
3701
3810
|
if (prUrl) {
|
|
@@ -3708,29 +3817,54 @@ async function monitorSession(sessionUrl, options = {}) {
|
|
|
3708
3817
|
status: "error",
|
|
3709
3818
|
error: error instanceof Error ? error.message : String(error)
|
|
3710
3819
|
};
|
|
3711
|
-
}
|
|
3712
|
-
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
function isValidBranchName(text) {
|
|
3823
|
+
if (!text) return false;
|
|
3824
|
+
const cleaned = text.trim();
|
|
3825
|
+
return cleaned.length > 2 && !cleaned.includes(" ");
|
|
3826
|
+
}
|
|
3827
|
+
function matchesBranchPattern(text) {
|
|
3828
|
+
return /^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+/.test(text) && !text.includes(" ");
|
|
3829
|
+
}
|
|
3830
|
+
async function tryExtractFromElement(page, selector) {
|
|
3831
|
+
try {
|
|
3832
|
+
const element = await page.$(selector);
|
|
3833
|
+
if (!element) return null;
|
|
3834
|
+
const text = await element.textContent();
|
|
3835
|
+
const cleaned = text?.trim() ?? null;
|
|
3836
|
+
return isValidBranchName(cleaned) ? cleaned : null;
|
|
3837
|
+
} catch {
|
|
3838
|
+
return null;
|
|
3713
3839
|
}
|
|
3714
3840
|
}
|
|
3715
3841
|
async function extractBranchName(page) {
|
|
3842
|
+
if (!page) return null;
|
|
3716
3843
|
const primarySelectors = [
|
|
3717
|
-
String.raw`button.group\/copy span.truncate
|
|
3844
|
+
String.raw`button.group\/copy span.truncate`,
|
|
3718
3845
|
// New branch name (verified)
|
|
3846
|
+
'button[class*="group/copy"] span.truncate',
|
|
3847
|
+
// Alternative escaping
|
|
3848
|
+
String.raw`button[class*="group\/copy"] span.truncate`
|
|
3849
|
+
// CSS escape
|
|
3719
3850
|
];
|
|
3720
3851
|
for (const selector of primarySelectors) {
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3852
|
+
const result = await tryExtractFromElement(page, selector);
|
|
3853
|
+
if (result) return result;
|
|
3854
|
+
}
|
|
3855
|
+
return await tryExtractBranchFromSpans(page);
|
|
3856
|
+
}
|
|
3857
|
+
async function tryExtractBranchFromSpans(page) {
|
|
3858
|
+
try {
|
|
3859
|
+
const allSpans = await page.$$("span.truncate");
|
|
3860
|
+
for (const span of allSpans) {
|
|
3861
|
+
const text = await span.textContent();
|
|
3862
|
+
const cleaned = text?.trim();
|
|
3863
|
+
if (cleaned && matchesBranchPattern(cleaned)) {
|
|
3864
|
+
return cleaned;
|
|
3731
3865
|
}
|
|
3732
|
-
} catch {
|
|
3733
3866
|
}
|
|
3867
|
+
} catch {
|
|
3734
3868
|
}
|
|
3735
3869
|
return null;
|
|
3736
3870
|
}
|
|
@@ -3768,6 +3902,36 @@ async function extractPrUrl(page) {
|
|
|
3768
3902
|
}
|
|
3769
3903
|
return null;
|
|
3770
3904
|
}
|
|
3905
|
+
async function detectActiveSpinner(page) {
|
|
3906
|
+
try {
|
|
3907
|
+
const spinner = await page.$(".code-spinner-animate");
|
|
3908
|
+
if (spinner) {
|
|
3909
|
+
return true;
|
|
3910
|
+
}
|
|
3911
|
+
const spinnerByContent = await page.$('span:has-text("\u273D")');
|
|
3912
|
+
if (spinnerByContent) {
|
|
3913
|
+
const hasAnimation = await spinnerByContent.evaluate((el) => {
|
|
3914
|
+
let current = el;
|
|
3915
|
+
while (current) {
|
|
3916
|
+
const classList = current.classList;
|
|
3917
|
+
if (classList?.contains("code-spinner-animate")) {
|
|
3918
|
+
return true;
|
|
3919
|
+
}
|
|
3920
|
+
const style = globalThis.getComputedStyle(current);
|
|
3921
|
+
if (style.animationName && style.animationName !== "none") {
|
|
3922
|
+
return true;
|
|
3923
|
+
}
|
|
3924
|
+
current = current.parentElement;
|
|
3925
|
+
}
|
|
3926
|
+
return false;
|
|
3927
|
+
});
|
|
3928
|
+
return hasAnimation;
|
|
3929
|
+
}
|
|
3930
|
+
return false;
|
|
3931
|
+
} catch {
|
|
3932
|
+
return false;
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3771
3935
|
|
|
3772
3936
|
// apps/cli/src/commands/auth.ts
|
|
3773
3937
|
async function authCommand() {
|
|
@@ -3851,6 +4015,7 @@ async function registerCommand(taskId, options) {
|
|
|
3851
4015
|
let viewUrl = options.viewUrl;
|
|
3852
4016
|
let teleportId = options.teleportId;
|
|
3853
4017
|
if (!process.stdin.isTTY) {
|
|
4018
|
+
console.log("\u23F3 Waiting for Claude session output...");
|
|
3854
4019
|
const input = await readStdin();
|
|
3855
4020
|
const parsed = parseClaudeOutput(input);
|
|
3856
4021
|
viewUrl = viewUrl || parsed.viewUrl;
|
|
@@ -3896,14 +4061,29 @@ Task ID: ${actualTaskId}`);
|
|
|
3896
4061
|
function readStdin() {
|
|
3897
4062
|
return new Promise((resolve) => {
|
|
3898
4063
|
let data = "";
|
|
4064
|
+
let resolved = false;
|
|
3899
4065
|
process.stdin.setEncoding("utf8");
|
|
3900
4066
|
process.stdin.on("data", (chunk) => {
|
|
3901
4067
|
data += chunk;
|
|
3902
4068
|
});
|
|
3903
4069
|
process.stdin.on("end", () => {
|
|
3904
|
-
|
|
4070
|
+
if (!resolved) {
|
|
4071
|
+
resolved = true;
|
|
4072
|
+
resolve(data);
|
|
4073
|
+
}
|
|
4074
|
+
});
|
|
4075
|
+
process.stdin.on("close", () => {
|
|
4076
|
+
if (!resolved) {
|
|
4077
|
+
resolved = true;
|
|
4078
|
+
resolve(data);
|
|
4079
|
+
}
|
|
3905
4080
|
});
|
|
3906
|
-
setTimeout(() =>
|
|
4081
|
+
setTimeout(() => {
|
|
4082
|
+
if (!resolved) {
|
|
4083
|
+
resolved = true;
|
|
4084
|
+
resolve(data);
|
|
4085
|
+
}
|
|
4086
|
+
}, 3e4);
|
|
3907
4087
|
});
|
|
3908
4088
|
}
|
|
3909
4089
|
function parseClaudeOutput(output) {
|
|
@@ -4066,17 +4246,23 @@ async function watchCommand(options) {
|
|
|
4066
4246
|
console.log(` Auto-PR: ${options.autoPr ? "enabled" : "disabled"}`);
|
|
4067
4247
|
console.log("");
|
|
4068
4248
|
const playwrightAvailable = await isPlaywrightAvailable();
|
|
4249
|
+
let browser = null;
|
|
4069
4250
|
if (!playwrightAvailable) {
|
|
4070
4251
|
console.log("\u26A0\uFE0F Playwright not installed. Session monitoring disabled.");
|
|
4071
4252
|
console.log(" Install with: pnpm add playwright && npx playwright install chromium");
|
|
4072
4253
|
console.log("");
|
|
4073
4254
|
} else {
|
|
4074
|
-
|
|
4255
|
+
browser = new PersistentBrowser(options.headless !== false);
|
|
4256
|
+
console.log("Checking Claude authentication...");
|
|
4257
|
+
const isAuthenticated = await browser.checkAuth();
|
|
4075
4258
|
if (!isAuthenticated) {
|
|
4076
4259
|
console.log("\u26A0\uFE0F Not logged into Claude. Run: flightdesk auth");
|
|
4077
4260
|
console.log("");
|
|
4261
|
+
await browser.close();
|
|
4262
|
+
browser = null;
|
|
4078
4263
|
} else {
|
|
4079
4264
|
console.log("\u2705 Playwright ready, Claude authenticated");
|
|
4265
|
+
console.log(" (Browser session kept alive for monitoring)");
|
|
4080
4266
|
console.log("");
|
|
4081
4267
|
}
|
|
4082
4268
|
}
|
|
@@ -4111,38 +4297,49 @@ async function watchCommand(options) {
|
|
|
4111
4297
|
if (reconciled) {
|
|
4112
4298
|
continue;
|
|
4113
4299
|
}
|
|
4114
|
-
if (task2.sessionViewUrl &&
|
|
4300
|
+
if (task2.sessionViewUrl && browser) {
|
|
4115
4301
|
console.log(` Checking session...`);
|
|
4116
|
-
const sessionInfo = await monitorSession(task2.sessionViewUrl, {
|
|
4117
|
-
headless: options.headless !== false,
|
|
4302
|
+
const sessionInfo = await browser.monitorSession(task2.sessionViewUrl, {
|
|
4118
4303
|
autoPr: options.autoPr && !task2.prUrl
|
|
4119
4304
|
// Only auto-PR if no PR exists
|
|
4120
4305
|
});
|
|
4121
4306
|
await processSessionInfo(api, task2, sessionInfo);
|
|
4122
4307
|
} else if (!task2.sessionViewUrl) {
|
|
4123
4308
|
console.log(" No session URL registered");
|
|
4309
|
+
} else if (!browser) {
|
|
4310
|
+
console.log(" Browser not available - cannot monitor session");
|
|
4124
4311
|
}
|
|
4125
4312
|
}
|
|
4126
4313
|
} catch (error) {
|
|
4127
4314
|
console.error(` Error: ${error}`);
|
|
4128
4315
|
}
|
|
4129
4316
|
}
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4317
|
+
try {
|
|
4318
|
+
await runCheck();
|
|
4319
|
+
if (options.once) {
|
|
4320
|
+
console.log("\nDone (--once flag specified)");
|
|
4321
|
+
return;
|
|
4322
|
+
}
|
|
4323
|
+
console.log(`
|
|
4136
4324
|
Watching... (Ctrl+C to stop)
|
|
4137
4325
|
`);
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4326
|
+
const intervalId = setInterval(runCheck, intervalMinutes * 60 * 1e3);
|
|
4327
|
+
process.on("SIGINT", async () => {
|
|
4328
|
+
console.log("\nShutting down...");
|
|
4329
|
+
clearInterval(intervalId);
|
|
4330
|
+
if (browser) {
|
|
4331
|
+
console.log("Closing browser...");
|
|
4332
|
+
await browser.close();
|
|
4333
|
+
}
|
|
4334
|
+
process.exit(0);
|
|
4335
|
+
});
|
|
4336
|
+
await new Promise((_resolve) => {
|
|
4337
|
+
});
|
|
4338
|
+
} finally {
|
|
4339
|
+
if (browser) {
|
|
4340
|
+
await browser.close();
|
|
4341
|
+
}
|
|
4342
|
+
}
|
|
4146
4343
|
}
|
|
4147
4344
|
async function reconcileTaskStatus(api, task2) {
|
|
4148
4345
|
let expectedStatus = null;
|
|
@@ -4186,7 +4383,25 @@ async function processSessionInfo(api, task2, info) {
|
|
|
4186
4383
|
console.log(" \u{1F4E6} Session archived");
|
|
4187
4384
|
return;
|
|
4188
4385
|
}
|
|
4189
|
-
|
|
4386
|
+
let activityIcon;
|
|
4387
|
+
let activityLabel;
|
|
4388
|
+
if (info.isActive === true) {
|
|
4389
|
+
activityIcon = "\u26A1";
|
|
4390
|
+
activityLabel = "Claude is working";
|
|
4391
|
+
} else if (info.isActive === false) {
|
|
4392
|
+
activityIcon = "\u{1F4A4}";
|
|
4393
|
+
activityLabel = "Waiting for input";
|
|
4394
|
+
} else {
|
|
4395
|
+
activityIcon = "\u2754";
|
|
4396
|
+
activityLabel = "Activity unknown";
|
|
4397
|
+
}
|
|
4398
|
+
console.log(` ${activityIcon} ${activityLabel}`);
|
|
4399
|
+
const updates = {
|
|
4400
|
+
// Update session activity state and timestamp
|
|
4401
|
+
// Preserve null when activity detection fails (info.isActive is undefined)
|
|
4402
|
+
sessionActive: info.isActive ?? null,
|
|
4403
|
+
sessionCheckedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4404
|
+
};
|
|
4190
4405
|
if (info.branchName && info.branchName !== task2.branchName) {
|
|
4191
4406
|
console.log(` \u{1F33F} Branch detected: ${info.branchName}`);
|
|
4192
4407
|
updates.branchName = info.branchName;
|
|
@@ -4206,15 +4421,14 @@ async function processSessionInfo(api, task2, info) {
|
|
|
4206
4421
|
if (info.hasPrButton && !info.prUrl && !task2.prUrl) {
|
|
4207
4422
|
console.log(' \u{1F4DD} "Create PR" button available');
|
|
4208
4423
|
}
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4424
|
+
const hasNonActivityUpdates = Object.keys(updates).some((k) => !["sessionActive", "sessionCheckedAt"].includes(k));
|
|
4425
|
+
try {
|
|
4426
|
+
await api.updateTask(task2.id, updates);
|
|
4427
|
+
if (hasNonActivityUpdates) {
|
|
4212
4428
|
console.log(" \u2705 Task updated");
|
|
4213
|
-
} catch (error) {
|
|
4214
|
-
console.log(` \u274C Failed to update task: ${error}`);
|
|
4215
4429
|
}
|
|
4216
|
-
}
|
|
4217
|
-
console.log(
|
|
4430
|
+
} catch (error) {
|
|
4431
|
+
console.log(` \u274C Failed to update task: ${error}`);
|
|
4218
4432
|
}
|
|
4219
4433
|
}
|
|
4220
4434
|
|
|
@@ -5300,7 +5514,7 @@ async function handleTeardown(api, options) {
|
|
|
5300
5514
|
|
|
5301
5515
|
// apps/cli/src/main.ts
|
|
5302
5516
|
var program2 = new Command();
|
|
5303
|
-
program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.2.
|
|
5517
|
+
program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.2.3").option("--dev", "Use local development API (localhost:3000)").option("--api <url>", "Use custom API URL");
|
|
5304
5518
|
program2.hook("preAction", () => {
|
|
5305
5519
|
const opts = program2.opts();
|
|
5306
5520
|
if (opts.api) {
|