flightdesk 0.1.6 → 0.1.9
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 +633 -618
- package/main.js.map +4 -4
- package/package.json +1 -1
package/main.js
CHANGED
|
@@ -3035,416 +3035,147 @@ 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
|
+
const config = loadConfig();
|
|
3056
|
+
return apiUrlOverride ?? config.apiUrl ?? DEFAULT_API_URL;
|
|
3057
|
+
}
|
|
3047
3058
|
function loadConfig() {
|
|
3048
3059
|
try {
|
|
3049
3060
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
3050
3061
|
const content = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
3051
|
-
|
|
3062
|
+
const parsed = JSON.parse(content);
|
|
3063
|
+
if (parsed.organizations?.[0]?.apiKey) {
|
|
3064
|
+
return migrateOldConfig(parsed);
|
|
3065
|
+
}
|
|
3066
|
+
return {
|
|
3067
|
+
apiUrl: DEFAULT_API_URL,
|
|
3068
|
+
organizations: [],
|
|
3069
|
+
repoMapping: {},
|
|
3070
|
+
...parsed
|
|
3071
|
+
};
|
|
3052
3072
|
}
|
|
3053
3073
|
} catch (error) {
|
|
3054
3074
|
console.error("Warning: Failed to load config file:", error);
|
|
3055
3075
|
}
|
|
3056
3076
|
return {
|
|
3077
|
+
apiUrl: DEFAULT_API_URL,
|
|
3057
3078
|
organizations: [],
|
|
3058
3079
|
repoMapping: {}
|
|
3059
3080
|
};
|
|
3060
3081
|
}
|
|
3082
|
+
function migrateOldConfig(oldConfig) {
|
|
3083
|
+
console.log("\u{1F4E6} Migrating config to new format...");
|
|
3084
|
+
const defaultOrg = oldConfig.defaultOrganization ? oldConfig.organizations.find((o) => o.id === oldConfig.defaultOrganization) : oldConfig.organizations[0];
|
|
3085
|
+
const newConfig = {
|
|
3086
|
+
apiKey: defaultOrg?.apiKey,
|
|
3087
|
+
apiUrl: defaultOrg?.apiUrl || DEFAULT_API_URL,
|
|
3088
|
+
activeOrganization: defaultOrg?.id,
|
|
3089
|
+
organizations: oldConfig.organizations.map((o) => ({
|
|
3090
|
+
id: o.id,
|
|
3091
|
+
name: o.name
|
|
3092
|
+
})),
|
|
3093
|
+
repoMapping: oldConfig.repoMapping || {}
|
|
3094
|
+
};
|
|
3095
|
+
saveConfig(newConfig);
|
|
3096
|
+
console.log("\u2705 Config migrated successfully");
|
|
3097
|
+
return newConfig;
|
|
3098
|
+
}
|
|
3061
3099
|
function saveConfig(config) {
|
|
3062
3100
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
3063
3101
|
}
|
|
3064
|
-
function
|
|
3065
|
-
const config = loadConfig();
|
|
3066
|
-
if (config.defaultOrganization) {
|
|
3067
|
-
return config.organizations.find((o) => o.id === config.defaultOrganization) || null;
|
|
3068
|
-
}
|
|
3069
|
-
if (config.organizations.length === 1) {
|
|
3070
|
-
return config.organizations[0];
|
|
3071
|
-
}
|
|
3072
|
-
return null;
|
|
3073
|
-
}
|
|
3074
|
-
function getOrganizationByRepo(repoFullName) {
|
|
3102
|
+
function getActiveOrganization() {
|
|
3075
3103
|
const config = loadConfig();
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3104
|
+
if (!config.activeOrganization) {
|
|
3105
|
+
if (config.organizations.length === 1) {
|
|
3106
|
+
return config.organizations[0];
|
|
3107
|
+
}
|
|
3108
|
+
return null;
|
|
3079
3109
|
}
|
|
3080
|
-
return null;
|
|
3110
|
+
return config.organizations.find((o) => o.id === config.activeOrganization) || null;
|
|
3081
3111
|
}
|
|
3082
|
-
function
|
|
3112
|
+
function setActiveOrganization(orgId) {
|
|
3083
3113
|
const config = loadConfig();
|
|
3084
|
-
const
|
|
3085
|
-
if (
|
|
3086
|
-
|
|
3087
|
-
} else {
|
|
3088
|
-
config.organizations.push(org2);
|
|
3089
|
-
}
|
|
3090
|
-
if (config.organizations.length === 1) {
|
|
3091
|
-
config.defaultOrganization = org2.id;
|
|
3114
|
+
const org2 = config.organizations.find((o) => o.id === orgId || o.name.toLowerCase() === orgId.toLowerCase());
|
|
3115
|
+
if (!org2) {
|
|
3116
|
+
throw new Error(`Organization not found: ${orgId}`);
|
|
3092
3117
|
}
|
|
3118
|
+
config.activeOrganization = org2.id;
|
|
3093
3119
|
saveConfig(config);
|
|
3094
3120
|
}
|
|
3095
|
-
function
|
|
3121
|
+
function updateOrganizations(orgs) {
|
|
3096
3122
|
const config = loadConfig();
|
|
3097
|
-
config.organizations =
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
delete config.repoMapping[repo];
|
|
3101
|
-
}
|
|
3102
|
-
}
|
|
3103
|
-
if (config.defaultOrganization === orgId) {
|
|
3104
|
-
config.defaultOrganization = config.organizations[0]?.id;
|
|
3123
|
+
config.organizations = orgs;
|
|
3124
|
+
if (config.activeOrganization && !orgs.find((o) => o.id === config.activeOrganization)) {
|
|
3125
|
+
config.activeOrganization = orgs[0]?.id;
|
|
3105
3126
|
}
|
|
3106
3127
|
saveConfig(config);
|
|
3107
3128
|
}
|
|
3108
|
-
function
|
|
3109
|
-
const config = loadConfig();
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
function
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
const rl = readline.createInterface({
|
|
3123
|
-
input: process.stdin,
|
|
3124
|
-
output: process.stdout
|
|
3125
|
-
});
|
|
3126
|
-
console.log("\n\u{1F6EB} FlightDesk CLI Setup\n");
|
|
3127
|
-
try {
|
|
3128
|
-
const config = loadConfig();
|
|
3129
|
-
if (config.organizations.length > 0) {
|
|
3130
|
-
console.log("Existing organizations configured:");
|
|
3131
|
-
config.organizations.forEach((org3, i) => {
|
|
3132
|
-
console.log(` ${i + 1}. ${org3.name} (${org3.id})`);
|
|
3133
|
-
});
|
|
3134
|
-
console.log("");
|
|
3135
|
-
const addMore = await question(rl, "Add another organization? (y/N): ");
|
|
3136
|
-
if (addMore.toLowerCase() !== "y") {
|
|
3137
|
-
console.log("\nConfiguration unchanged.");
|
|
3138
|
-
rl.close();
|
|
3139
|
-
return;
|
|
3140
|
-
}
|
|
3141
|
-
}
|
|
3142
|
-
const orgName = await question(rl, "Organization name: ");
|
|
3143
|
-
if (!orgName) {
|
|
3144
|
-
console.error("Organization name is required");
|
|
3145
|
-
rl.close();
|
|
3146
|
-
return;
|
|
3147
|
-
}
|
|
3148
|
-
const apiKey = await question(rl, "API Key: ");
|
|
3149
|
-
if (!apiKey) {
|
|
3150
|
-
console.error("API Key is required");
|
|
3151
|
-
rl.close();
|
|
3152
|
-
return;
|
|
3153
|
-
}
|
|
3154
|
-
const apiUrlInput = await question(rl, `API URL (${DEFAULT_API_URL}): `);
|
|
3155
|
-
const apiUrl = apiUrlInput || DEFAULT_API_URL;
|
|
3156
|
-
let orgId;
|
|
3157
|
-
const keyMatch = apiKey.match(/^fd_key_([^_]+)_/);
|
|
3158
|
-
if (keyMatch) {
|
|
3159
|
-
orgId = keyMatch[1];
|
|
3160
|
-
} else {
|
|
3161
|
-
orgId = `org_${Date.now()}`;
|
|
3162
|
-
}
|
|
3163
|
-
const org2 = {
|
|
3164
|
-
id: orgId,
|
|
3165
|
-
name: orgName,
|
|
3166
|
-
apiKey,
|
|
3167
|
-
apiUrl
|
|
3168
|
-
};
|
|
3169
|
-
console.log("\nTesting connection...");
|
|
3170
|
-
try {
|
|
3171
|
-
const response = await fetch(`${apiUrl}/graphql`, {
|
|
3172
|
-
method: "POST",
|
|
3173
|
-
headers: {
|
|
3174
|
-
"Content-Type": "application/json",
|
|
3175
|
-
"Authorization": `Bearer ${apiKey}`
|
|
3176
|
-
},
|
|
3177
|
-
body: JSON.stringify({
|
|
3178
|
-
query: "{ __typename }"
|
|
3179
|
-
})
|
|
3180
|
-
});
|
|
3181
|
-
if (!response.ok) {
|
|
3182
|
-
throw new Error(`API returned ${response.status}`);
|
|
3183
|
-
}
|
|
3184
|
-
console.log("\u2705 Connection successful!");
|
|
3185
|
-
} catch (error) {
|
|
3186
|
-
console.error(`\u274C Connection failed: ${error}`);
|
|
3187
|
-
const proceed = await question(rl, "Save configuration anyway? (y/N): ");
|
|
3188
|
-
if (proceed.toLowerCase() !== "y") {
|
|
3189
|
-
rl.close();
|
|
3190
|
-
return;
|
|
3191
|
-
}
|
|
3192
|
-
}
|
|
3193
|
-
addOrganization(org2);
|
|
3194
|
-
if (config.organizations.length > 0) {
|
|
3195
|
-
const setDefault = await question(rl, "Set as default organization? (y/N): ");
|
|
3196
|
-
if (setDefault.toLowerCase() === "y") {
|
|
3197
|
-
const updatedConfig = loadConfig();
|
|
3198
|
-
updatedConfig.defaultOrganization = orgId;
|
|
3199
|
-
saveConfig(updatedConfig);
|
|
3200
|
-
}
|
|
3201
|
-
}
|
|
3202
|
-
console.log(`
|
|
3203
|
-
\u2705 Configuration saved to ${CONFIG_FILE}`);
|
|
3204
|
-
console.log("\nYou can now use:");
|
|
3205
|
-
console.log(' flightdesk task create -p <project-id> -t "Task title"');
|
|
3206
|
-
console.log(" flightdesk status");
|
|
3207
|
-
console.log(" flightdesk watch");
|
|
3208
|
-
} finally {
|
|
3209
|
-
rl.close();
|
|
3210
|
-
}
|
|
3211
|
-
}
|
|
3212
|
-
|
|
3213
|
-
// 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"));
|
|
3217
|
-
var playwright = null;
|
|
3218
|
-
var USER_DATA_DIR = path2.join(os2.homedir(), ".flightdesk", "chromium-profile");
|
|
3219
|
-
async function isPlaywrightAvailable() {
|
|
3220
|
-
try {
|
|
3221
|
-
playwright = await import("playwright");
|
|
3222
|
-
return true;
|
|
3223
|
-
} catch {
|
|
3224
|
-
return false;
|
|
3225
|
-
}
|
|
3226
|
-
}
|
|
3227
|
-
function ensureUserDataDir() {
|
|
3228
|
-
if (!fs2.existsSync(USER_DATA_DIR)) {
|
|
3229
|
-
fs2.mkdirSync(USER_DATA_DIR, { recursive: true });
|
|
3230
|
-
}
|
|
3231
|
-
}
|
|
3232
|
-
async function launchBrowser(headless) {
|
|
3233
|
-
if (!playwright) {
|
|
3234
|
-
throw new Error("Playwright not available");
|
|
3235
|
-
}
|
|
3236
|
-
ensureUserDataDir();
|
|
3237
|
-
const context = await playwright.chromium.launchPersistentContext(USER_DATA_DIR, {
|
|
3238
|
-
headless,
|
|
3239
|
-
viewport: { width: 1280, height: 720 },
|
|
3240
|
-
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"
|
|
3241
|
-
});
|
|
3242
|
-
const browser = context.browser();
|
|
3243
|
-
return { browser, context };
|
|
3244
|
-
}
|
|
3245
|
-
async function checkAuth() {
|
|
3246
|
-
if (!await isPlaywrightAvailable()) {
|
|
3247
|
-
throw new Error("Playwright not installed");
|
|
3248
|
-
}
|
|
3249
|
-
const { context } = await launchBrowser(true);
|
|
3250
|
-
try {
|
|
3251
|
-
const page = await context.newPage();
|
|
3252
|
-
page.setDefaultTimeout(3e4);
|
|
3253
|
-
await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
3254
|
-
await page.waitForTimeout(3e3);
|
|
3255
|
-
const url = page.url();
|
|
3256
|
-
console.log(" Final URL:", url);
|
|
3257
|
-
const isLoggedIn = !url.includes("/login") && !url.includes("/oauth") && !url.includes("accounts.google") && url.includes("claude.ai");
|
|
3258
|
-
return isLoggedIn;
|
|
3259
|
-
} catch (error) {
|
|
3260
|
-
console.error("Auth check failed:", error.message);
|
|
3261
|
-
return false;
|
|
3262
|
-
} finally {
|
|
3263
|
-
await context.close();
|
|
3264
|
-
}
|
|
3265
|
-
}
|
|
3266
|
-
async function openForLogin() {
|
|
3267
|
-
if (!await isPlaywrightAvailable()) {
|
|
3268
|
-
throw new Error("Playwright not installed");
|
|
3269
|
-
}
|
|
3270
|
-
console.log("Opening browser for Claude login...");
|
|
3271
|
-
const { context } = await launchBrowser(false);
|
|
3272
|
-
try {
|
|
3273
|
-
const page = await context.newPage();
|
|
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;
|
|
3285
|
-
} finally {
|
|
3286
|
-
try {
|
|
3287
|
-
await context.close();
|
|
3288
|
-
} catch {
|
|
3289
|
-
}
|
|
3290
|
-
}
|
|
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
|
-
}
|
|
3305
|
-
async function monitorSession(sessionUrl, options = {}) {
|
|
3306
|
-
if (!await isPlaywrightAvailable()) {
|
|
3307
|
-
return { status: "error", error: "Playwright not installed" };
|
|
3308
|
-
}
|
|
3309
|
-
const { headless = true, timeout = 3e4, autoPr = false } = options;
|
|
3310
|
-
const { context } = await launchBrowser(headless);
|
|
3311
|
-
try {
|
|
3312
|
-
const page = await context.newPage();
|
|
3313
|
-
page.setDefaultTimeout(timeout);
|
|
3314
|
-
await page.goto(sessionUrl, { waitUntil: "domcontentloaded" });
|
|
3315
|
-
await page.waitForTimeout(2e3);
|
|
3316
|
-
const result = { status: "active" };
|
|
3317
|
-
const archiveIndicator = await page.$("text=This session has been archived");
|
|
3318
|
-
if (archiveIndicator) {
|
|
3319
|
-
return { status: "archived" };
|
|
3320
|
-
}
|
|
3321
|
-
const url = page.url();
|
|
3322
|
-
if (url.includes("/login") || url.includes("/oauth")) {
|
|
3323
|
-
return {
|
|
3324
|
-
status: "error",
|
|
3325
|
-
error: "Not logged in to Claude. Run: flightdesk auth"
|
|
3326
|
-
};
|
|
3327
|
-
}
|
|
3328
|
-
const branchName = await extractBranchName(page);
|
|
3329
|
-
if (branchName) {
|
|
3330
|
-
result.branchName = branchName;
|
|
3331
|
-
}
|
|
3332
|
-
const prButton = await page.$('button:has-text("Create PR"):not([aria-haspopup])');
|
|
3333
|
-
result.hasPrButton = !!prButton;
|
|
3334
|
-
if (autoPr && prButton) {
|
|
3335
|
-
console.log(' Clicking "Create PR" button...');
|
|
3336
|
-
await prButton.click();
|
|
3337
|
-
await page.waitForTimeout(5e3);
|
|
3338
|
-
const prUrl = await extractPrUrl(page);
|
|
3339
|
-
if (prUrl) {
|
|
3340
|
-
result.prUrl = prUrl;
|
|
3341
|
-
}
|
|
3342
|
-
}
|
|
3343
|
-
return result;
|
|
3344
|
-
} catch (error) {
|
|
3345
|
-
return {
|
|
3346
|
-
status: "error",
|
|
3347
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3348
|
-
};
|
|
3349
|
-
} finally {
|
|
3350
|
-
await context.close();
|
|
3351
|
-
}
|
|
3352
|
-
}
|
|
3353
|
-
async function extractBranchName(page) {
|
|
3354
|
-
const primarySelectors = [
|
|
3355
|
-
"button.group\\/copy span.truncate"
|
|
3356
|
-
// New branch name (verified)
|
|
3357
|
-
];
|
|
3358
|
-
for (const selector of primarySelectors) {
|
|
3359
|
-
try {
|
|
3360
|
-
const element = await page.$(selector);
|
|
3361
|
-
if (element) {
|
|
3362
|
-
const text = await element.textContent();
|
|
3363
|
-
if (text) {
|
|
3364
|
-
const cleaned = text.trim();
|
|
3365
|
-
if (cleaned && cleaned.length > 2 && !cleaned.includes(" ")) {
|
|
3366
|
-
return cleaned;
|
|
3367
|
-
}
|
|
3368
|
-
}
|
|
3369
|
-
}
|
|
3370
|
-
} catch {
|
|
3371
|
-
}
|
|
3372
|
-
}
|
|
3373
|
-
return null;
|
|
3374
|
-
}
|
|
3375
|
-
async function extractPrUrl(page) {
|
|
3376
|
-
const selectors = [
|
|
3377
|
-
'a[href*="github.com"][href*="/pull/"]',
|
|
3378
|
-
'a[href*="gitlab.com"][href*="/merge_requests/"]',
|
|
3379
|
-
'a[href*="bitbucket.org"][href*="/pull-requests/"]'
|
|
3380
|
-
];
|
|
3381
|
-
for (const selector of selectors) {
|
|
3382
|
-
try {
|
|
3383
|
-
const element = await page.$(selector);
|
|
3384
|
-
if (element) {
|
|
3385
|
-
const href = await element.getAttribute("href");
|
|
3386
|
-
if (href) {
|
|
3387
|
-
return href;
|
|
3388
|
-
}
|
|
3389
|
-
}
|
|
3390
|
-
} catch {
|
|
3391
|
-
}
|
|
3392
|
-
}
|
|
3393
|
-
try {
|
|
3394
|
-
const pageContent = await page.content();
|
|
3395
|
-
const prPatterns = [
|
|
3396
|
-
/(https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+)/,
|
|
3397
|
-
/(https:\/\/gitlab\.com\/[^/]+\/[^/]+\/-\/merge_requests\/\d+)/
|
|
3398
|
-
];
|
|
3399
|
-
for (const pattern of prPatterns) {
|
|
3400
|
-
const match = pageContent.match(pattern);
|
|
3401
|
-
if (match && match[1]) {
|
|
3402
|
-
return match[1];
|
|
3403
|
-
}
|
|
3404
|
-
}
|
|
3405
|
-
} catch {
|
|
3406
|
-
}
|
|
3407
|
-
return null;
|
|
3408
|
-
}
|
|
3409
|
-
|
|
3410
|
-
// apps/cli/src/commands/auth.ts
|
|
3411
|
-
async function authCommand() {
|
|
3412
|
-
console.log("\u{1F510} FlightDesk Authentication\n");
|
|
3413
|
-
const playwrightAvailable = await isPlaywrightAvailable();
|
|
3414
|
-
if (!playwrightAvailable) {
|
|
3415
|
-
console.error("Playwright is not installed.");
|
|
3416
|
-
console.error("Install with: pnpm add playwright && npx playwright install chromium");
|
|
3129
|
+
function getOrganizationByRepo(repoFullName) {
|
|
3130
|
+
const config = loadConfig();
|
|
3131
|
+
const orgId = config.repoMapping[repoFullName];
|
|
3132
|
+
if (!orgId) return null;
|
|
3133
|
+
return config.organizations.find((o) => o.id === orgId) || null;
|
|
3134
|
+
}
|
|
3135
|
+
function isConfigured() {
|
|
3136
|
+
const config = loadConfig();
|
|
3137
|
+
return !!config.apiKey && config.organizations.length > 0;
|
|
3138
|
+
}
|
|
3139
|
+
function requireConfig() {
|
|
3140
|
+
const config = loadConfig();
|
|
3141
|
+
if (!config.apiKey) {
|
|
3142
|
+
console.error("\u274C FlightDesk is not configured. Run: flightdesk init");
|
|
3417
3143
|
process.exit(1);
|
|
3418
3144
|
}
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
const isAuthenticated = await checkAuth();
|
|
3423
|
-
if (isAuthenticated) {
|
|
3424
|
-
console.log("\u2705 Already logged in to Claude!");
|
|
3425
|
-
console.log("\nThe watch daemon will be able to monitor your sessions.");
|
|
3426
|
-
return;
|
|
3145
|
+
if (config.organizations.length === 0) {
|
|
3146
|
+
console.error("\u274C No organizations found. Run: flightdesk init");
|
|
3147
|
+
process.exit(1);
|
|
3427
3148
|
}
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
const
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
console.
|
|
3435
|
-
|
|
3436
|
-
console.log(
|
|
3437
|
-
|
|
3149
|
+
return config;
|
|
3150
|
+
}
|
|
3151
|
+
function requireActiveOrg() {
|
|
3152
|
+
const config = requireConfig();
|
|
3153
|
+
const org2 = getActiveOrganization();
|
|
3154
|
+
if (!org2) {
|
|
3155
|
+
console.error("\u274C No active organization. Run: flightdesk org switch <org-name>");
|
|
3156
|
+
console.log("\nAvailable organizations:");
|
|
3157
|
+
config.organizations.forEach((o) => console.log(` - ${o.name}`));
|
|
3158
|
+
process.exit(1);
|
|
3438
3159
|
}
|
|
3160
|
+
return { config, org: org2 };
|
|
3439
3161
|
}
|
|
3440
3162
|
|
|
3163
|
+
// apps/cli/src/commands/init.ts
|
|
3164
|
+
var readline = __toESM(require("readline"));
|
|
3165
|
+
|
|
3441
3166
|
// apps/cli/src/lib/api.ts
|
|
3442
|
-
var FlightDeskAPI = class {
|
|
3443
|
-
constructor(org2) {
|
|
3444
|
-
this.apiUrl =
|
|
3445
|
-
this.apiKey =
|
|
3167
|
+
var FlightDeskAPI = class _FlightDeskAPI {
|
|
3168
|
+
constructor(config, org2) {
|
|
3169
|
+
this.apiUrl = getApiUrl();
|
|
3170
|
+
this.apiKey = config.apiKey;
|
|
3446
3171
|
this.organizationId = org2.id;
|
|
3447
3172
|
}
|
|
3173
|
+
/**
|
|
3174
|
+
* Create an API client from config, using the active organization
|
|
3175
|
+
*/
|
|
3176
|
+
static fromConfig(config, org2) {
|
|
3177
|
+
return new _FlightDeskAPI(config, org2);
|
|
3178
|
+
}
|
|
3448
3179
|
async graphql(query, variables) {
|
|
3449
3180
|
const response = await fetch(`${this.apiUrl}/graphql`, {
|
|
3450
3181
|
method: "POST",
|
|
@@ -3589,71 +3320,424 @@ var FlightDeskAPI = class {
|
|
|
3589
3320
|
tokenTtlHours
|
|
3590
3321
|
}
|
|
3591
3322
|
}
|
|
3592
|
-
`;
|
|
3593
|
-
const result = await this.graphql(query, { projectId });
|
|
3594
|
-
return result.userProject;
|
|
3323
|
+
`;
|
|
3324
|
+
const result = await this.graphql(query, { projectId });
|
|
3325
|
+
return result.userProject;
|
|
3326
|
+
}
|
|
3327
|
+
async createProject(input) {
|
|
3328
|
+
const query = `
|
|
3329
|
+
mutation CreateProject($input: UserCreateProjectInput!) {
|
|
3330
|
+
userCreateProject(input: $input) {
|
|
3331
|
+
id
|
|
3332
|
+
name
|
|
3333
|
+
githubRepo
|
|
3334
|
+
organizationId
|
|
3335
|
+
createdAt
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
`;
|
|
3339
|
+
const result = await this.graphql(query, { input });
|
|
3340
|
+
return result.userCreateProject;
|
|
3341
|
+
}
|
|
3342
|
+
// ============================================================================
|
|
3343
|
+
// Task Token Operations (for agent authentication)
|
|
3344
|
+
// ============================================================================
|
|
3345
|
+
async createTaskToken(taskId) {
|
|
3346
|
+
const query = `
|
|
3347
|
+
mutation CreateTaskToken($taskId: String!) {
|
|
3348
|
+
userCreateTaskToken(taskId: $taskId) {
|
|
3349
|
+
token
|
|
3350
|
+
expiresAt
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
`;
|
|
3354
|
+
const result = await this.graphql(query, { taskId });
|
|
3355
|
+
return result.userCreateTaskToken;
|
|
3356
|
+
}
|
|
3357
|
+
// ============================================================================
|
|
3358
|
+
// Prompt Operations
|
|
3359
|
+
// ============================================================================
|
|
3360
|
+
async getTaskPrompts(taskId) {
|
|
3361
|
+
const query = `
|
|
3362
|
+
query GetTaskPrompts($taskId: String!) {
|
|
3363
|
+
userTaskPrompts(taskId: $taskId) {
|
|
3364
|
+
type
|
|
3365
|
+
content
|
|
3366
|
+
available
|
|
3367
|
+
reason
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
`;
|
|
3371
|
+
const result = await this.graphql(query, { taskId });
|
|
3372
|
+
return result.userTaskPrompts;
|
|
3373
|
+
}
|
|
3374
|
+
};
|
|
3375
|
+
async function fetchUserInfo(apiKey, apiUrl) {
|
|
3376
|
+
const url = apiUrl || "https://api.flightdesk.dev";
|
|
3377
|
+
const response = await fetch(`${url}/graphql`, {
|
|
3378
|
+
method: "POST",
|
|
3379
|
+
headers: {
|
|
3380
|
+
"Content-Type": "application/json",
|
|
3381
|
+
"Authorization": `Bearer ${apiKey}`
|
|
3382
|
+
},
|
|
3383
|
+
body: JSON.stringify({
|
|
3384
|
+
query: `{
|
|
3385
|
+
me {
|
|
3386
|
+
id
|
|
3387
|
+
email
|
|
3388
|
+
organizations {
|
|
3389
|
+
organization {
|
|
3390
|
+
id
|
|
3391
|
+
name
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
}`
|
|
3396
|
+
})
|
|
3397
|
+
});
|
|
3398
|
+
if (!response.ok) {
|
|
3399
|
+
throw new Error(`API returned ${response.status}`);
|
|
3400
|
+
}
|
|
3401
|
+
const result = await response.json();
|
|
3402
|
+
if (result.errors?.length) {
|
|
3403
|
+
throw new Error(result.errors.map((e) => e.message).join(", "));
|
|
3404
|
+
}
|
|
3405
|
+
if (!result.data?.me) {
|
|
3406
|
+
throw new Error("Invalid API response");
|
|
3407
|
+
}
|
|
3408
|
+
return result.data.me;
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
// apps/cli/src/commands/init.ts
|
|
3412
|
+
function question(rl, prompt) {
|
|
3413
|
+
return new Promise((resolve) => {
|
|
3414
|
+
rl.question(prompt, (answer) => {
|
|
3415
|
+
resolve(answer.trim());
|
|
3416
|
+
});
|
|
3417
|
+
});
|
|
3418
|
+
}
|
|
3419
|
+
async function initCommand() {
|
|
3420
|
+
const rl = readline.createInterface({
|
|
3421
|
+
input: process.stdin,
|
|
3422
|
+
output: process.stdout
|
|
3423
|
+
});
|
|
3424
|
+
console.log("\n\u{1F6EB} FlightDesk CLI Setup\n");
|
|
3425
|
+
try {
|
|
3426
|
+
if (isConfigured()) {
|
|
3427
|
+
const config = loadConfig();
|
|
3428
|
+
console.log("FlightDesk is already configured.");
|
|
3429
|
+
console.log(` API Key: ${config.apiKey?.substring(0, 20)}...`);
|
|
3430
|
+
console.log(` Organizations: ${config.organizations.map((o) => o.name).join(", ")}`);
|
|
3431
|
+
console.log("");
|
|
3432
|
+
const reconfigure = await question(rl, "Reconfigure? (y/N): ");
|
|
3433
|
+
if (reconfigure.toLowerCase() !== "y") {
|
|
3434
|
+
console.log("\nConfiguration unchanged.");
|
|
3435
|
+
console.log('Use "flightdesk org refresh" to update organizations.');
|
|
3436
|
+
console.log('Use "flightdesk org switch <name>" to change active organization.');
|
|
3437
|
+
return;
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
console.log("Enter your FlightDesk API key.");
|
|
3441
|
+
console.log("You can find this at https://flightdesk.dev/settings/api-keys\n");
|
|
3442
|
+
const apiKey = await question(rl, "API Key: ");
|
|
3443
|
+
if (!apiKey) {
|
|
3444
|
+
console.error("API Key is required");
|
|
3445
|
+
return;
|
|
3446
|
+
}
|
|
3447
|
+
const apiUrl = getApiUrl();
|
|
3448
|
+
console.log("\nConnecting to FlightDesk...");
|
|
3449
|
+
try {
|
|
3450
|
+
const userInfo = await fetchUserInfo(apiKey, apiUrl);
|
|
3451
|
+
if (userInfo.organizations.length === 0) {
|
|
3452
|
+
console.error("\u274C No organizations found for this user");
|
|
3453
|
+
console.log("Please make sure you are a member of at least one organization.");
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
console.log("\u2705 Connection successful!");
|
|
3457
|
+
console.log(` Logged in as: ${userInfo.email}`);
|
|
3458
|
+
console.log(` Organizations: ${userInfo.organizations.length}`);
|
|
3459
|
+
const organizations = userInfo.organizations.map((m) => ({
|
|
3460
|
+
id: m.organization.id,
|
|
3461
|
+
name: m.organization.name
|
|
3462
|
+
}));
|
|
3463
|
+
let activeOrganization;
|
|
3464
|
+
if (organizations.length === 1) {
|
|
3465
|
+
activeOrganization = organizations[0].id;
|
|
3466
|
+
console.log(`
|
|
3467
|
+
Active organization: ${organizations[0].name}`);
|
|
3468
|
+
} else {
|
|
3469
|
+
console.log("\nYour organizations:");
|
|
3470
|
+
organizations.forEach((org2, i) => {
|
|
3471
|
+
console.log(` ${i + 1}. ${org2.name}`);
|
|
3472
|
+
});
|
|
3473
|
+
const choice = await question(rl, `
|
|
3474
|
+
Select active organization (1-${organizations.length}): `);
|
|
3475
|
+
const choiceIndex = parseInt(choice, 10) - 1;
|
|
3476
|
+
if (choiceIndex < 0 || choiceIndex >= organizations.length) {
|
|
3477
|
+
console.error("Invalid selection, using first organization");
|
|
3478
|
+
activeOrganization = organizations[0].id;
|
|
3479
|
+
} else {
|
|
3480
|
+
activeOrganization = organizations[choiceIndex].id;
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
const config = loadConfig();
|
|
3484
|
+
config.apiKey = apiKey;
|
|
3485
|
+
config.apiUrl = apiUrl;
|
|
3486
|
+
config.organizations = organizations;
|
|
3487
|
+
config.activeOrganization = activeOrganization;
|
|
3488
|
+
saveConfig(config);
|
|
3489
|
+
console.log(`
|
|
3490
|
+
\u2705 Configuration saved to ${CONFIG_FILE}`);
|
|
3491
|
+
console.log("\nYou can now use:");
|
|
3492
|
+
console.log(" flightdesk project list - List projects");
|
|
3493
|
+
console.log(" flightdesk task create - Create a task");
|
|
3494
|
+
console.log(" flightdesk org list - Show organizations");
|
|
3495
|
+
console.log(" flightdesk org switch <name> - Switch active organization");
|
|
3496
|
+
} catch (error) {
|
|
3497
|
+
console.error(`
|
|
3498
|
+
\u274C Connection failed: ${error}`);
|
|
3499
|
+
console.log("\nPlease check:");
|
|
3500
|
+
console.log(" - Your API key is correct");
|
|
3501
|
+
console.log(" - You have network connectivity");
|
|
3502
|
+
console.log(" - The FlightDesk API is reachable");
|
|
3503
|
+
}
|
|
3504
|
+
} finally {
|
|
3505
|
+
rl.close();
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
// apps/cli/src/lib/session-monitor.ts
|
|
3510
|
+
var path2 = __toESM(require("node:path"));
|
|
3511
|
+
var os2 = __toESM(require("node:os"));
|
|
3512
|
+
var fs2 = __toESM(require("node:fs"));
|
|
3513
|
+
var readline2 = __toESM(require("node:readline"));
|
|
3514
|
+
var playwright = null;
|
|
3515
|
+
var USER_DATA_DIR = path2.join(os2.homedir(), ".flightdesk", "chromium-profile");
|
|
3516
|
+
async function isPlaywrightAvailable() {
|
|
3517
|
+
try {
|
|
3518
|
+
playwright = await import("playwright");
|
|
3519
|
+
return true;
|
|
3520
|
+
} catch {
|
|
3521
|
+
return false;
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
function ensureUserDataDir() {
|
|
3525
|
+
if (!fs2.existsSync(USER_DATA_DIR)) {
|
|
3526
|
+
fs2.mkdirSync(USER_DATA_DIR, { recursive: true });
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
async function launchBrowser(headless) {
|
|
3530
|
+
if (!playwright) {
|
|
3531
|
+
throw new Error("Playwright not available");
|
|
3532
|
+
}
|
|
3533
|
+
ensureUserDataDir();
|
|
3534
|
+
const context = await playwright.chromium.launchPersistentContext(USER_DATA_DIR, {
|
|
3535
|
+
headless,
|
|
3536
|
+
viewport: { width: 1280, height: 720 },
|
|
3537
|
+
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"
|
|
3538
|
+
});
|
|
3539
|
+
const browser = context.browser();
|
|
3540
|
+
return { browser, context };
|
|
3541
|
+
}
|
|
3542
|
+
async function checkAuth() {
|
|
3543
|
+
if (!await isPlaywrightAvailable()) {
|
|
3544
|
+
throw new Error("Playwright not installed");
|
|
3545
|
+
}
|
|
3546
|
+
const { context } = await launchBrowser(true);
|
|
3547
|
+
try {
|
|
3548
|
+
const page = await context.newPage();
|
|
3549
|
+
page.setDefaultTimeout(3e4);
|
|
3550
|
+
await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
3551
|
+
await page.waitForTimeout(3e3);
|
|
3552
|
+
const url = page.url();
|
|
3553
|
+
console.log(" Final URL:", url);
|
|
3554
|
+
const isLoggedIn = !url.includes("/login") && !url.includes("/oauth") && !url.includes("accounts.google") && url.includes("claude.ai");
|
|
3555
|
+
return isLoggedIn;
|
|
3556
|
+
} catch (error) {
|
|
3557
|
+
console.error("Auth check failed:", error.message);
|
|
3558
|
+
return false;
|
|
3559
|
+
} finally {
|
|
3560
|
+
await context.close();
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
async function openForLogin() {
|
|
3564
|
+
if (!await isPlaywrightAvailable()) {
|
|
3565
|
+
throw new Error("Playwright not installed");
|
|
3566
|
+
}
|
|
3567
|
+
console.log("Opening browser for Claude login...");
|
|
3568
|
+
const { context } = await launchBrowser(false);
|
|
3569
|
+
try {
|
|
3570
|
+
const page = await context.newPage();
|
|
3571
|
+
await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded", timeout: 6e4 });
|
|
3572
|
+
console.log("\n\u{1F449} Press ENTER here when you have logged in...\n");
|
|
3573
|
+
await waitForEnter();
|
|
3574
|
+
console.log("Verifying login in current session...");
|
|
3575
|
+
await page.goto("https://claude.ai/", { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
3576
|
+
await page.waitForTimeout(2e3);
|
|
3577
|
+
const url = page.url();
|
|
3578
|
+
console.log(" Final URL:", url);
|
|
3579
|
+
const isLoggedIn = !url.includes("/login") && !url.includes("/oauth") && !url.includes("accounts.google") && url.includes("claude.ai");
|
|
3580
|
+
console.log("Closing browser...");
|
|
3581
|
+
return isLoggedIn;
|
|
3582
|
+
} finally {
|
|
3583
|
+
try {
|
|
3584
|
+
await context.close();
|
|
3585
|
+
} catch {
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
function waitForEnter() {
|
|
3590
|
+
return new Promise((resolve) => {
|
|
3591
|
+
const rl = readline2.createInterface({
|
|
3592
|
+
input: process.stdin,
|
|
3593
|
+
output: process.stdout
|
|
3594
|
+
});
|
|
3595
|
+
rl.question("", () => {
|
|
3596
|
+
rl.close();
|
|
3597
|
+
resolve();
|
|
3598
|
+
});
|
|
3599
|
+
});
|
|
3600
|
+
}
|
|
3601
|
+
async function monitorSession(sessionUrl, options = {}) {
|
|
3602
|
+
if (!await isPlaywrightAvailable()) {
|
|
3603
|
+
return { status: "error", error: "Playwright not installed" };
|
|
3604
|
+
}
|
|
3605
|
+
const { headless = true, timeout = 3e4, autoPr = false } = options;
|
|
3606
|
+
const { context } = await launchBrowser(headless);
|
|
3607
|
+
try {
|
|
3608
|
+
const page = await context.newPage();
|
|
3609
|
+
page.setDefaultTimeout(timeout);
|
|
3610
|
+
await page.goto(sessionUrl, { waitUntil: "domcontentloaded" });
|
|
3611
|
+
await page.waitForTimeout(2e3);
|
|
3612
|
+
const result = { status: "active" };
|
|
3613
|
+
const archiveIndicator = await page.$("text=This session has been archived");
|
|
3614
|
+
if (archiveIndicator) {
|
|
3615
|
+
return { status: "archived" };
|
|
3616
|
+
}
|
|
3617
|
+
const url = page.url();
|
|
3618
|
+
if (url.includes("/login") || url.includes("/oauth")) {
|
|
3619
|
+
return {
|
|
3620
|
+
status: "error",
|
|
3621
|
+
error: "Not logged in to Claude. Run: flightdesk auth"
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
const branchName = await extractBranchName(page);
|
|
3625
|
+
if (branchName) {
|
|
3626
|
+
result.branchName = branchName;
|
|
3627
|
+
}
|
|
3628
|
+
const prButton = await page.$('button:has-text("Create PR"):not([aria-haspopup])');
|
|
3629
|
+
result.hasPrButton = !!prButton;
|
|
3630
|
+
if (autoPr && prButton) {
|
|
3631
|
+
console.log(' Clicking "Create PR" button...');
|
|
3632
|
+
await prButton.click();
|
|
3633
|
+
await page.waitForTimeout(5e3);
|
|
3634
|
+
const prUrl = await extractPrUrl(page);
|
|
3635
|
+
if (prUrl) {
|
|
3636
|
+
result.prUrl = prUrl;
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
return result;
|
|
3640
|
+
} catch (error) {
|
|
3641
|
+
return {
|
|
3642
|
+
status: "error",
|
|
3643
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3644
|
+
};
|
|
3645
|
+
} finally {
|
|
3646
|
+
await context.close();
|
|
3595
3647
|
}
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3648
|
+
}
|
|
3649
|
+
async function extractBranchName(page) {
|
|
3650
|
+
const primarySelectors = [
|
|
3651
|
+
String.raw`button.group\/copy span.truncate`
|
|
3652
|
+
// New branch name (verified)
|
|
3653
|
+
];
|
|
3654
|
+
for (const selector of primarySelectors) {
|
|
3655
|
+
try {
|
|
3656
|
+
const element = await page.$(selector);
|
|
3657
|
+
if (element) {
|
|
3658
|
+
const text = await element.textContent();
|
|
3659
|
+
if (text) {
|
|
3660
|
+
const cleaned = text.trim();
|
|
3661
|
+
if (cleaned && cleaned.length > 2 && !cleaned.includes(" ")) {
|
|
3662
|
+
return cleaned;
|
|
3663
|
+
}
|
|
3605
3664
|
}
|
|
3606
3665
|
}
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
return result.userCreateProject;
|
|
3666
|
+
} catch {
|
|
3667
|
+
}
|
|
3610
3668
|
}
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3669
|
+
return null;
|
|
3670
|
+
}
|
|
3671
|
+
async function extractPrUrl(page) {
|
|
3672
|
+
const selectors = [
|
|
3673
|
+
'a[href*="github.com"][href*="/pull/"]',
|
|
3674
|
+
'a[href*="gitlab.com"][href*="/merge_requests/"]',
|
|
3675
|
+
'a[href*="bitbucket.org"][href*="/pull-requests/"]'
|
|
3676
|
+
];
|
|
3677
|
+
for (const selector of selectors) {
|
|
3678
|
+
try {
|
|
3679
|
+
const element = await page.$(selector);
|
|
3680
|
+
if (element) {
|
|
3681
|
+
const href = await element.getAttribute("href");
|
|
3682
|
+
if (href) {
|
|
3683
|
+
return href;
|
|
3620
3684
|
}
|
|
3621
3685
|
}
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
return result.userCreateTaskToken;
|
|
3686
|
+
} catch {
|
|
3687
|
+
}
|
|
3625
3688
|
}
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
reason
|
|
3637
|
-
}
|
|
3689
|
+
try {
|
|
3690
|
+
const pageContent = await page.content();
|
|
3691
|
+
const prPatterns = [
|
|
3692
|
+
/(https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+)/,
|
|
3693
|
+
/(https:\/\/gitlab\.com\/[^/]+\/[^/]+\/-\/merge_requests\/\d+)/
|
|
3694
|
+
];
|
|
3695
|
+
for (const pattern of prPatterns) {
|
|
3696
|
+
const match = pageContent.match(pattern);
|
|
3697
|
+
if (match && match[1]) {
|
|
3698
|
+
return match[1];
|
|
3638
3699
|
}
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
return result.userTaskPrompts;
|
|
3700
|
+
}
|
|
3701
|
+
} catch {
|
|
3642
3702
|
}
|
|
3643
|
-
|
|
3703
|
+
return null;
|
|
3704
|
+
}
|
|
3644
3705
|
|
|
3645
|
-
// apps/cli/src/commands/
|
|
3646
|
-
async function
|
|
3647
|
-
|
|
3648
|
-
|
|
3706
|
+
// apps/cli/src/commands/auth.ts
|
|
3707
|
+
async function authCommand() {
|
|
3708
|
+
console.log("\u{1F510} FlightDesk Authentication\n");
|
|
3709
|
+
const playwrightAvailable = await isPlaywrightAvailable();
|
|
3710
|
+
if (!playwrightAvailable) {
|
|
3711
|
+
console.error("Playwright is not installed.");
|
|
3712
|
+
console.error("Install with: pnpm add playwright && npx playwright install chromium");
|
|
3649
3713
|
process.exit(1);
|
|
3650
3714
|
}
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3715
|
+
console.log(`Profile directory: ${USER_DATA_DIR}
|
|
3716
|
+
`);
|
|
3717
|
+
console.log("Checking current authentication status...");
|
|
3718
|
+
const isAuthenticated = await checkAuth();
|
|
3719
|
+
if (isAuthenticated) {
|
|
3720
|
+
console.log("\u2705 Already logged in to Claude!");
|
|
3721
|
+
console.log("\nThe watch daemon will be able to monitor your sessions.");
|
|
3722
|
+
return;
|
|
3723
|
+
}
|
|
3724
|
+
console.log("\u274C Not logged in to Claude.\n");
|
|
3725
|
+
console.log("Opening browser for login...");
|
|
3726
|
+
console.log("Please log in to your Claude account.\n");
|
|
3727
|
+
const loginSuccessful = await openForLogin();
|
|
3728
|
+
if (loginSuccessful) {
|
|
3729
|
+
console.log("\n\u2705 Successfully logged in!");
|
|
3730
|
+
console.log("The watch daemon can now monitor your Claude Code sessions.");
|
|
3731
|
+
} else {
|
|
3732
|
+
console.log("\n\u274C Login was not detected.");
|
|
3733
|
+
console.log("Please try again with: flightdesk auth");
|
|
3655
3734
|
}
|
|
3656
|
-
|
|
3735
|
+
}
|
|
3736
|
+
|
|
3737
|
+
// apps/cli/src/commands/register.ts
|
|
3738
|
+
async function registerCommand(projectId, taskId, options) {
|
|
3739
|
+
const { config, org: org2 } = requireActiveOrg();
|
|
3740
|
+
const api = FlightDeskAPI.fromConfig(config, org2);
|
|
3657
3741
|
try {
|
|
3658
3742
|
let viewUrl = options.viewUrl;
|
|
3659
3743
|
let teleportId = options.teleportId;
|
|
@@ -3728,16 +3812,8 @@ function parseClaudeOutput(output) {
|
|
|
3728
3812
|
|
|
3729
3813
|
// apps/cli/src/commands/status.ts
|
|
3730
3814
|
async function statusCommand(options) {
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
process.exit(1);
|
|
3734
|
-
}
|
|
3735
|
-
const org2 = getDefaultOrganization();
|
|
3736
|
-
if (!org2) {
|
|
3737
|
-
console.error("No organization configured. Run: flightdesk init");
|
|
3738
|
-
process.exit(1);
|
|
3739
|
-
}
|
|
3740
|
-
const api = new FlightDeskAPI(org2);
|
|
3815
|
+
const { config, org: org2 } = requireActiveOrg();
|
|
3816
|
+
const api = FlightDeskAPI.fromConfig(config, org2);
|
|
3741
3817
|
try {
|
|
3742
3818
|
const tasks = await api.listTasks({ projectId: options.project });
|
|
3743
3819
|
if (tasks.length === 0) {
|
|
@@ -3822,15 +3898,7 @@ function getStatusEmoji(status) {
|
|
|
3822
3898
|
|
|
3823
3899
|
// apps/cli/src/commands/watch.ts
|
|
3824
3900
|
async function watchCommand(options) {
|
|
3825
|
-
|
|
3826
|
-
console.error("FlightDesk is not configured. Run: flightdesk init");
|
|
3827
|
-
process.exit(1);
|
|
3828
|
-
}
|
|
3829
|
-
const org2 = getDefaultOrganization();
|
|
3830
|
-
if (!org2) {
|
|
3831
|
-
console.error("No organization configured. Run: flightdesk init");
|
|
3832
|
-
process.exit(1);
|
|
3833
|
-
}
|
|
3901
|
+
const { config, org: org2 } = requireActiveOrg();
|
|
3834
3902
|
const intervalMinutes = parseInt(options.interval, 10);
|
|
3835
3903
|
if (isNaN(intervalMinutes) || intervalMinutes < 1) {
|
|
3836
3904
|
console.error("Invalid interval. Must be a positive number of minutes.");
|
|
@@ -3856,7 +3924,7 @@ async function watchCommand(options) {
|
|
|
3856
3924
|
console.log("");
|
|
3857
3925
|
}
|
|
3858
3926
|
}
|
|
3859
|
-
const api =
|
|
3927
|
+
const api = FlightDeskAPI.fromConfig(config, org2);
|
|
3860
3928
|
async function runCheck() {
|
|
3861
3929
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
3862
3930
|
console.log(`[${timestamp}] Checking tasks...`);
|
|
@@ -3950,16 +4018,8 @@ async function processSessionInfo(api, task2, info) {
|
|
|
3950
4018
|
|
|
3951
4019
|
// apps/cli/src/commands/task.ts
|
|
3952
4020
|
async function taskCommand(action, options) {
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
process.exit(1);
|
|
3956
|
-
}
|
|
3957
|
-
const org2 = getDefaultOrganization();
|
|
3958
|
-
if (!org2) {
|
|
3959
|
-
console.error("No organization configured. Run: flightdesk init");
|
|
3960
|
-
process.exit(1);
|
|
3961
|
-
}
|
|
3962
|
-
const api = new FlightDeskAPI(org2);
|
|
4021
|
+
const { config, org: org2 } = requireActiveOrg();
|
|
4022
|
+
const api = FlightDeskAPI.fromConfig(config, org2);
|
|
3963
4023
|
try {
|
|
3964
4024
|
switch (action) {
|
|
3965
4025
|
case "create":
|
|
@@ -4095,16 +4155,8 @@ function getStatusEmoji2(status) {
|
|
|
4095
4155
|
|
|
4096
4156
|
// apps/cli/src/commands/prompt.ts
|
|
4097
4157
|
async function promptCommand(taskId, options) {
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
process.exit(1);
|
|
4101
|
-
}
|
|
4102
|
-
const org2 = getDefaultOrganization();
|
|
4103
|
-
if (!org2) {
|
|
4104
|
-
console.error("No organization configured. Run: flightdesk init");
|
|
4105
|
-
process.exit(1);
|
|
4106
|
-
}
|
|
4107
|
-
const api = new FlightDeskAPI(org2);
|
|
4158
|
+
const { config, org: org2 } = requireActiveOrg();
|
|
4159
|
+
const api = FlightDeskAPI.fromConfig(config, org2);
|
|
4108
4160
|
const validTypes = ["review", "test_plan", "summary", "handoff"];
|
|
4109
4161
|
if (!validTypes.includes(options.type)) {
|
|
4110
4162
|
console.error(`Invalid prompt type: ${options.type}`);
|
|
@@ -4130,148 +4182,107 @@ async function promptCommand(taskId, options) {
|
|
|
4130
4182
|
}
|
|
4131
4183
|
|
|
4132
4184
|
// apps/cli/src/commands/org.ts
|
|
4133
|
-
var readline2 = __toESM(require("readline"));
|
|
4134
|
-
function question2(rl, prompt) {
|
|
4135
|
-
return new Promise((resolve) => {
|
|
4136
|
-
rl.question(prompt, (answer) => {
|
|
4137
|
-
resolve(answer.trim());
|
|
4138
|
-
});
|
|
4139
|
-
});
|
|
4140
|
-
}
|
|
4141
4185
|
async function orgListCommand() {
|
|
4186
|
+
if (!isConfigured()) {
|
|
4187
|
+
console.log("FlightDesk is not configured.");
|
|
4188
|
+
console.log("Run: flightdesk init");
|
|
4189
|
+
return;
|
|
4190
|
+
}
|
|
4142
4191
|
const config = loadConfig();
|
|
4192
|
+
const activeOrg = getActiveOrganization();
|
|
4193
|
+
console.log("\n\u{1F4CB} Organizations\n");
|
|
4143
4194
|
if (config.organizations.length === 0) {
|
|
4144
|
-
console.log("No organizations
|
|
4145
|
-
console.log("Run: flightdesk
|
|
4195
|
+
console.log("No organizations found.");
|
|
4196
|
+
console.log("Run: flightdesk org refresh");
|
|
4146
4197
|
return;
|
|
4147
4198
|
}
|
|
4148
|
-
console.log("\n\u{1F4CB} Connected Organizations\n");
|
|
4149
4199
|
for (const org2 of config.organizations) {
|
|
4150
|
-
const
|
|
4151
|
-
const
|
|
4152
|
-
console.log(` ${org2.name}${
|
|
4153
|
-
console.log(` ID: ${org2.id}`);
|
|
4154
|
-
console.log(` API: ${org2.apiUrl}`);
|
|
4155
|
-
console.log("");
|
|
4156
|
-
}
|
|
4157
|
-
const mappings = Object.entries(config.repoMapping);
|
|
4158
|
-
if (mappings.length > 0) {
|
|
4159
|
-
console.log("\u{1F4C1} Repository Mappings\n");
|
|
4160
|
-
for (const [repo, orgId] of mappings) {
|
|
4161
|
-
const org2 = config.organizations.find((o) => o.id === orgId);
|
|
4162
|
-
console.log(` ${repo} \u2192 ${org2?.name || orgId}`);
|
|
4163
|
-
}
|
|
4164
|
-
console.log("");
|
|
4200
|
+
const isActive = org2.id === activeOrg?.id;
|
|
4201
|
+
const activeTag = isActive ? " \u2190 active" : "";
|
|
4202
|
+
console.log(` ${isActive ? "\u25CF" : "\u25CB"} ${org2.name}${activeTag}`);
|
|
4165
4203
|
}
|
|
4204
|
+
console.log("");
|
|
4205
|
+
console.log("Commands:");
|
|
4206
|
+
console.log(" flightdesk org switch <name> Switch active organization");
|
|
4207
|
+
console.log(" flightdesk org refresh Refresh organizations from API");
|
|
4208
|
+
console.log("");
|
|
4166
4209
|
}
|
|
4167
|
-
async function
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
console.log("\n\u{1F517} Add Organization\n");
|
|
4173
|
-
try {
|
|
4174
|
-
const orgName = await question2(rl, "Organization name: ");
|
|
4175
|
-
if (!orgName) {
|
|
4176
|
-
console.error("Organization name is required");
|
|
4177
|
-
return;
|
|
4178
|
-
}
|
|
4179
|
-
const apiKey = await question2(rl, "API Key: ");
|
|
4180
|
-
if (!apiKey) {
|
|
4181
|
-
console.error("API Key is required");
|
|
4182
|
-
return;
|
|
4183
|
-
}
|
|
4184
|
-
const apiUrlInput = await question2(rl, `API URL (${DEFAULT_API_URL}): `);
|
|
4185
|
-
const apiUrl = apiUrlInput || DEFAULT_API_URL;
|
|
4186
|
-
let orgId;
|
|
4187
|
-
const keyMatch = apiKey.match(/^fd_key_([^_]+)_/);
|
|
4188
|
-
if (keyMatch) {
|
|
4189
|
-
orgId = keyMatch[1];
|
|
4190
|
-
} else {
|
|
4191
|
-
orgId = `org_${Date.now()}`;
|
|
4192
|
-
}
|
|
4193
|
-
const org2 = {
|
|
4194
|
-
id: orgId,
|
|
4195
|
-
name: orgName,
|
|
4196
|
-
apiKey,
|
|
4197
|
-
apiUrl
|
|
4198
|
-
};
|
|
4199
|
-
console.log("\nTesting connection...");
|
|
4200
|
-
try {
|
|
4201
|
-
const response = await fetch(`${apiUrl}/graphql`, {
|
|
4202
|
-
method: "POST",
|
|
4203
|
-
headers: {
|
|
4204
|
-
"Content-Type": "application/json",
|
|
4205
|
-
"Authorization": `Bearer ${apiKey}`
|
|
4206
|
-
},
|
|
4207
|
-
body: JSON.stringify({
|
|
4208
|
-
query: "{ __typename }"
|
|
4209
|
-
})
|
|
4210
|
-
});
|
|
4211
|
-
if (!response.ok) {
|
|
4212
|
-
throw new Error(`API returned ${response.status}`);
|
|
4213
|
-
}
|
|
4214
|
-
console.log("\u2705 Connection successful!");
|
|
4215
|
-
} catch (error) {
|
|
4216
|
-
console.error(`\u274C Connection failed: ${error}`);
|
|
4217
|
-
const proceed = await question2(rl, "Save anyway? (y/N): ");
|
|
4218
|
-
if (proceed.toLowerCase() !== "y") {
|
|
4219
|
-
return;
|
|
4220
|
-
}
|
|
4221
|
-
}
|
|
4222
|
-
addOrganization(org2);
|
|
4223
|
-
console.log(`
|
|
4224
|
-
\u2705 Added organization: ${orgName}`);
|
|
4225
|
-
const config = loadConfig();
|
|
4226
|
-
if (config.organizations.length > 1) {
|
|
4227
|
-
const setDefault = await question2(rl, "Set as default? (y/N): ");
|
|
4228
|
-
if (setDefault.toLowerCase() === "y") {
|
|
4229
|
-
config.defaultOrganization = orgId;
|
|
4230
|
-
saveConfig(config);
|
|
4231
|
-
console.log("Set as default organization");
|
|
4232
|
-
}
|
|
4233
|
-
}
|
|
4234
|
-
} finally {
|
|
4235
|
-
rl.close();
|
|
4210
|
+
async function orgSwitchCommand(orgIdentifier) {
|
|
4211
|
+
if (!isConfigured()) {
|
|
4212
|
+
console.log("FlightDesk is not configured.");
|
|
4213
|
+
console.log("Run: flightdesk init");
|
|
4214
|
+
return;
|
|
4236
4215
|
}
|
|
4237
|
-
}
|
|
4238
|
-
async function orgRemoveCommand(orgId) {
|
|
4239
4216
|
const config = loadConfig();
|
|
4240
|
-
const org2 = config.organizations.find(
|
|
4217
|
+
const org2 = config.organizations.find(
|
|
4218
|
+
(o) => o.id === orgIdentifier || o.name.toLowerCase() === orgIdentifier.toLowerCase()
|
|
4219
|
+
);
|
|
4241
4220
|
if (!org2) {
|
|
4242
|
-
console.error(`Organization not found: ${
|
|
4221
|
+
console.error(`Organization not found: ${orgIdentifier}`);
|
|
4243
4222
|
console.log("\nAvailable organizations:");
|
|
4244
4223
|
for (const o of config.organizations) {
|
|
4245
|
-
console.log(` - ${o.name}
|
|
4224
|
+
console.log(` - ${o.name}`);
|
|
4246
4225
|
}
|
|
4247
4226
|
process.exit(1);
|
|
4248
4227
|
}
|
|
4249
|
-
const
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
try {
|
|
4254
|
-
const confirm = await question2(rl, `Remove "${org2.name}"? (y/N): `);
|
|
4255
|
-
if (confirm.toLowerCase() !== "y") {
|
|
4256
|
-
console.log("Cancelled");
|
|
4257
|
-
return;
|
|
4258
|
-
}
|
|
4259
|
-
removeOrganization(org2.id);
|
|
4260
|
-
console.log(`\u2705 Removed organization: ${org2.name}`);
|
|
4261
|
-
} finally {
|
|
4262
|
-
rl.close();
|
|
4228
|
+
const currentOrg = getActiveOrganization();
|
|
4229
|
+
if (currentOrg?.id === org2.id) {
|
|
4230
|
+
console.log(`Already using organization: ${org2.name}`);
|
|
4231
|
+
return;
|
|
4263
4232
|
}
|
|
4233
|
+
setActiveOrganization(org2.id);
|
|
4234
|
+
console.log(`\u2705 Switched to organization: ${org2.name}`);
|
|
4264
4235
|
}
|
|
4265
|
-
async function
|
|
4236
|
+
async function orgRefreshCommand() {
|
|
4237
|
+
if (!isConfigured()) {
|
|
4238
|
+
console.log("FlightDesk is not configured.");
|
|
4239
|
+
console.log("Run: flightdesk init");
|
|
4240
|
+
return;
|
|
4241
|
+
}
|
|
4266
4242
|
const config = loadConfig();
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
console.
|
|
4270
|
-
|
|
4243
|
+
if (!config.apiKey) {
|
|
4244
|
+
console.error("No API key configured.");
|
|
4245
|
+
console.log("Run: flightdesk init");
|
|
4246
|
+
return;
|
|
4247
|
+
}
|
|
4248
|
+
console.log("Refreshing organizations...");
|
|
4249
|
+
try {
|
|
4250
|
+
const apiUrl = getApiUrl();
|
|
4251
|
+
const userInfo = await fetchUserInfo(config.apiKey, apiUrl);
|
|
4252
|
+
if (userInfo.organizations.length === 0) {
|
|
4253
|
+
console.log("No organizations found for this user.");
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
const organizations = userInfo.organizations.map((m) => ({
|
|
4257
|
+
id: m.organization.id,
|
|
4258
|
+
name: m.organization.name
|
|
4259
|
+
}));
|
|
4260
|
+
const currentIds = new Set(config.organizations.map((o) => o.id));
|
|
4261
|
+
const newOrgs = organizations.filter((o) => !currentIds.has(o.id));
|
|
4262
|
+
const newIds = new Set(organizations.map((o) => o.id));
|
|
4263
|
+
const removedOrgs = config.organizations.filter((o) => !newIds.has(o.id));
|
|
4264
|
+
updateOrganizations(organizations);
|
|
4265
|
+
console.log(`\u2705 Found ${organizations.length} organization(s)`);
|
|
4266
|
+
if (newOrgs.length > 0) {
|
|
4267
|
+
console.log("\n New organizations:");
|
|
4268
|
+
for (const org2 of newOrgs) {
|
|
4269
|
+
console.log(` + ${org2.name}`);
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
if (removedOrgs.length > 0) {
|
|
4273
|
+
console.log("\n Removed organizations:");
|
|
4274
|
+
for (const org2 of removedOrgs) {
|
|
4275
|
+
console.log(` - ${org2.name}`);
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4278
|
+
const activeOrg = getActiveOrganization();
|
|
4279
|
+
if (activeOrg) {
|
|
4280
|
+
console.log(`
|
|
4281
|
+
Active: ${activeOrg.name}`);
|
|
4282
|
+
}
|
|
4283
|
+
} catch (error) {
|
|
4284
|
+
console.error(`\u274C Failed to refresh: ${error}`);
|
|
4271
4285
|
}
|
|
4272
|
-
config.defaultOrganization = org2.id;
|
|
4273
|
-
saveConfig(config);
|
|
4274
|
-
console.log(`\u2705 Default organization set to: ${org2.name}`);
|
|
4275
4286
|
}
|
|
4276
4287
|
|
|
4277
4288
|
// apps/cli/src/commands/context.ts
|
|
@@ -4307,11 +4318,12 @@ async function contextCommand() {
|
|
|
4307
4318
|
console.log("\u{1F4C1} Not in a git repository (or no remote configured)");
|
|
4308
4319
|
console.log("");
|
|
4309
4320
|
const config = loadConfig();
|
|
4321
|
+
const activeOrg2 = getActiveOrganization();
|
|
4310
4322
|
if (config.organizations.length > 0) {
|
|
4311
|
-
console.log("
|
|
4312
|
-
for (const
|
|
4313
|
-
const
|
|
4314
|
-
console.log(`
|
|
4323
|
+
console.log("Organizations:");
|
|
4324
|
+
for (const org2 of config.organizations) {
|
|
4325
|
+
const isActive = org2.id === activeOrg2?.id;
|
|
4326
|
+
console.log(` ${isActive ? "\u25CF" : "\u25CB"} ${org2.name}${isActive ? " (active)" : ""}`);
|
|
4315
4327
|
}
|
|
4316
4328
|
} else {
|
|
4317
4329
|
console.log("No organizations configured. Run: flightdesk init");
|
|
@@ -4321,10 +4333,11 @@ async function contextCommand() {
|
|
|
4321
4333
|
console.log(`\u{1F4C1} Repository: ${repoInfo.remote}`);
|
|
4322
4334
|
console.log(`\u{1F33F} Branch: ${repoInfo.branch}`);
|
|
4323
4335
|
console.log("");
|
|
4324
|
-
const
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
console.log(
|
|
4336
|
+
const mappedOrg = getOrganizationByRepo(repoInfo.remote);
|
|
4337
|
+
const activeOrg = getActiveOrganization();
|
|
4338
|
+
if (mappedOrg) {
|
|
4339
|
+
console.log(`\u{1F3E2} Organization: ${mappedOrg.name}`);
|
|
4340
|
+
console.log(` API: ${getApiUrl()}`);
|
|
4328
4341
|
console.log("");
|
|
4329
4342
|
console.log("This repository is mapped to the above organization.");
|
|
4330
4343
|
console.log("Commands will use this organization automatically.");
|
|
@@ -4333,10 +4346,10 @@ async function contextCommand() {
|
|
|
4333
4346
|
console.log("");
|
|
4334
4347
|
const config = loadConfig();
|
|
4335
4348
|
if (config.organizations.length > 0) {
|
|
4336
|
-
console.log("
|
|
4337
|
-
for (const
|
|
4338
|
-
const
|
|
4339
|
-
console.log(`
|
|
4349
|
+
console.log("Organizations:");
|
|
4350
|
+
for (const org2 of config.organizations) {
|
|
4351
|
+
const isActive = org2.id === activeOrg?.id;
|
|
4352
|
+
console.log(` ${isActive ? "\u25CF" : "\u25CB"} ${org2.name}${isActive ? " (active)" : ""}`);
|
|
4340
4353
|
}
|
|
4341
4354
|
console.log("");
|
|
4342
4355
|
console.log("Run: flightdesk sync to refresh repository mappings");
|
|
@@ -4348,7 +4361,7 @@ async function contextCommand() {
|
|
|
4348
4361
|
|
|
4349
4362
|
// apps/cli/src/commands/sync.ts
|
|
4350
4363
|
async function syncCommand() {
|
|
4351
|
-
const config =
|
|
4364
|
+
const config = requireConfig();
|
|
4352
4365
|
if (config.organizations.length === 0) {
|
|
4353
4366
|
console.error("No organizations configured. Run: flightdesk init");
|
|
4354
4367
|
process.exit(1);
|
|
@@ -4359,7 +4372,7 @@ async function syncCommand() {
|
|
|
4359
4372
|
for (const org2 of config.organizations) {
|
|
4360
4373
|
console.log(` ${org2.name}...`);
|
|
4361
4374
|
try {
|
|
4362
|
-
const api =
|
|
4375
|
+
const api = FlightDeskAPI.fromConfig(config, org2);
|
|
4363
4376
|
const projects = await api.listProjects();
|
|
4364
4377
|
console.log(` Found ${projects.length} project(s)`);
|
|
4365
4378
|
for (const project of projects) {
|
|
@@ -4395,13 +4408,13 @@ var ClaudeSelectors = {
|
|
|
4395
4408
|
/** New task input (textarea in sidebar) */
|
|
4396
4409
|
newTaskInput: 'textarea[placeholder="Ask Claude to write code..."]',
|
|
4397
4410
|
/** Session list container */
|
|
4398
|
-
sessionList:
|
|
4411
|
+
sessionList: String.raw`.flex.flex-col.gap-0\.5.px-1`,
|
|
4399
4412
|
/** Individual session items - use :has-text() with title */
|
|
4400
4413
|
sessionItem: (title) => `.cursor-pointer:has-text("${title}")`,
|
|
4401
4414
|
/** All session items (div elements with cursor-pointer class) */
|
|
4402
|
-
allSessions:
|
|
4415
|
+
allSessions: String.raw`.flex.flex-col.gap-0\.5.px-1 .cursor-pointer`,
|
|
4403
4416
|
/** Session by index (0-indexed) */
|
|
4404
|
-
sessionByIndex: (index) => `.flex.flex-col.gap-0
|
|
4417
|
+
sessionByIndex: (index) => String.raw`.flex.flex-col.gap-0\.5.px-1` + ` > div:nth-child(${index + 1}) .cursor-pointer`,
|
|
4405
4418
|
/** Active/selected session (has bg-bg-300 class) */
|
|
4406
4419
|
activeSession: ".cursor-pointer.bg-bg-300",
|
|
4407
4420
|
/** Session title within a session item */
|
|
@@ -4426,7 +4439,7 @@ var ClaudeSelectors = {
|
|
|
4426
4439
|
/** Copy branch button (click to copy branch name) */
|
|
4427
4440
|
copyBranchButton: "button.group\\/copy",
|
|
4428
4441
|
/** Diff stats button (+N -M) */
|
|
4429
|
-
diffStatsButton:
|
|
4442
|
+
diffStatsButton: String.raw`.flex.items-center.gap-2.w-full.p-2 button.border-0\.5`
|
|
4430
4443
|
},
|
|
4431
4444
|
// ============================================================================
|
|
4432
4445
|
// Pull Request Controls - Verified 2026-02-23
|
|
@@ -4543,15 +4556,7 @@ var ClaudeSelectors = {
|
|
|
4543
4556
|
var fs3 = __toESM(require("fs"));
|
|
4544
4557
|
var playwright2 = null;
|
|
4545
4558
|
async function importCommand(options) {
|
|
4546
|
-
|
|
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
|
-
}
|
|
4559
|
+
const { config, org: org2 } = requireActiveOrg();
|
|
4555
4560
|
if (!await isPlaywrightAvailable()) {
|
|
4556
4561
|
console.error("Playwright is required for import. Install it with:");
|
|
4557
4562
|
console.error(" npm install -g playwright");
|
|
@@ -4559,7 +4564,7 @@ async function importCommand(options) {
|
|
|
4559
4564
|
process.exit(1);
|
|
4560
4565
|
}
|
|
4561
4566
|
playwright2 = await import("playwright");
|
|
4562
|
-
const api =
|
|
4567
|
+
const api = FlightDeskAPI.fromConfig(config, org2);
|
|
4563
4568
|
console.log("\n\u{1F50D} FlightDesk Import - Scanning Claude Sessions\n");
|
|
4564
4569
|
if (options.dryRun) {
|
|
4565
4570
|
console.log("\u{1F4CB} DRY RUN - No changes will be made\n");
|
|
@@ -4599,7 +4604,7 @@ Found ${sessions.length} sessions:
|
|
|
4599
4604
|
}
|
|
4600
4605
|
for (const [repoName, repoSessions] of sessionsByRepo) {
|
|
4601
4606
|
const matchingProject = existingProjects.find(
|
|
4602
|
-
(p) => p.githubRepo
|
|
4607
|
+
(p) => p.githubRepo?.endsWith(`/${repoName}`)
|
|
4603
4608
|
);
|
|
4604
4609
|
if (matchingProject) {
|
|
4605
4610
|
console.log(`\u{1F4C1} ${repoName} \u2192 Project: ${matchingProject.name} (${matchingProject.id.slice(0, 8)})`);
|
|
@@ -4641,7 +4646,7 @@ Summary:`);
|
|
|
4641
4646
|
const importableSessions = [];
|
|
4642
4647
|
for (const [repoName, repoSessions] of sessionsByRepo) {
|
|
4643
4648
|
const matchingProject = existingProjects.find(
|
|
4644
|
-
(p) => p.githubRepo
|
|
4649
|
+
(p) => p.githubRepo?.endsWith(`/${repoName}`)
|
|
4645
4650
|
);
|
|
4646
4651
|
if (matchingProject) {
|
|
4647
4652
|
for (const session of repoSessions) {
|
|
@@ -4786,7 +4791,18 @@ async function scanClaudeSessions(options) {
|
|
|
4786
4791
|
|
|
4787
4792
|
// apps/cli/src/main.ts
|
|
4788
4793
|
var program2 = new Command();
|
|
4789
|
-
program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.1.
|
|
4794
|
+
program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.1.9").option("--dev", "Use local development API (localhost:3000)").option("--api <url>", "Use custom API URL");
|
|
4795
|
+
program2.hook("preAction", () => {
|
|
4796
|
+
const opts = program2.opts();
|
|
4797
|
+
if (opts.api) {
|
|
4798
|
+
setApiUrl(opts.api);
|
|
4799
|
+
console.log(`\u{1F527} Using custom API: ${opts.api}
|
|
4800
|
+
`);
|
|
4801
|
+
} else if (opts.dev) {
|
|
4802
|
+
setDevMode(true);
|
|
4803
|
+
console.log("\u{1F527} Development mode: using http://localhost:3000\n");
|
|
4804
|
+
}
|
|
4805
|
+
});
|
|
4790
4806
|
program2.command("init").description("Configure FlightDesk CLI with your API credentials").action(initCommand);
|
|
4791
4807
|
program2.command("auth").description("Log in to Claude for session monitoring").action(authCommand);
|
|
4792
4808
|
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);
|
|
@@ -4800,10 +4816,9 @@ program2.command("import").description("Scan Claude.ai for existing sessions and
|
|
|
4800
4816
|
program2.command("status").description("Show status of all active tasks").option("-p, --project <id>", "Filter by project").action(statusCommand);
|
|
4801
4817
|
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);
|
|
4802
4818
|
var org = program2.command("org").description("Organization management");
|
|
4803
|
-
org.command("list").description("List
|
|
4804
|
-
org.command("
|
|
4805
|
-
org.command("
|
|
4806
|
-
org.command("default <org-id>").description("Set default organization").action(orgSetDefaultCommand);
|
|
4819
|
+
org.command("list").description("List your organizations").action(orgListCommand);
|
|
4820
|
+
org.command("switch <name>").description("Switch active organization").action(orgSwitchCommand);
|
|
4821
|
+
org.command("refresh").description("Refresh organizations from API").action(orgRefreshCommand);
|
|
4807
4822
|
program2.command("context").description("Show current repository context and mapped organization").action(contextCommand);
|
|
4808
4823
|
program2.command("sync").description("Refresh project-to-repository mappings from all organizations").action(syncCommand);
|
|
4809
4824
|
program2.command("mount <task-id>").description("Mount preview environment filesystem via SSHFS").option("--vscode", "Open in VS Code after mounting").action((taskId) => {
|