flightdesk 0.1.14 → 0.2.1
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 +457 -32
- package/main.js.map +4 -4
- package/package.json +1 -1
package/main.js
CHANGED
|
@@ -958,8 +958,8 @@ var require_command = __commonJS({
|
|
|
958
958
|
"node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/command.js"(exports2) {
|
|
959
959
|
var EventEmitter = require("node:events").EventEmitter;
|
|
960
960
|
var childProcess = require("node:child_process");
|
|
961
|
-
var
|
|
962
|
-
var
|
|
961
|
+
var path4 = require("node:path");
|
|
962
|
+
var fs5 = require("node:fs");
|
|
963
963
|
var process2 = require("node:process");
|
|
964
964
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
965
965
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -1891,11 +1891,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1891
1891
|
let launchWithNode = false;
|
|
1892
1892
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1893
1893
|
function findFile(baseDir, baseName) {
|
|
1894
|
-
const localBin =
|
|
1895
|
-
if (
|
|
1896
|
-
if (sourceExt.includes(
|
|
1894
|
+
const localBin = path4.resolve(baseDir, baseName);
|
|
1895
|
+
if (fs5.existsSync(localBin)) return localBin;
|
|
1896
|
+
if (sourceExt.includes(path4.extname(baseName))) return void 0;
|
|
1897
1897
|
const foundExt = sourceExt.find(
|
|
1898
|
-
(ext) =>
|
|
1898
|
+
(ext) => fs5.existsSync(`${localBin}${ext}`)
|
|
1899
1899
|
);
|
|
1900
1900
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
1901
1901
|
return void 0;
|
|
@@ -1907,21 +1907,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1907
1907
|
if (this._scriptPath) {
|
|
1908
1908
|
let resolvedScriptPath;
|
|
1909
1909
|
try {
|
|
1910
|
-
resolvedScriptPath =
|
|
1910
|
+
resolvedScriptPath = fs5.realpathSync(this._scriptPath);
|
|
1911
1911
|
} catch (err) {
|
|
1912
1912
|
resolvedScriptPath = this._scriptPath;
|
|
1913
1913
|
}
|
|
1914
|
-
executableDir =
|
|
1915
|
-
|
|
1914
|
+
executableDir = path4.resolve(
|
|
1915
|
+
path4.dirname(resolvedScriptPath),
|
|
1916
1916
|
executableDir
|
|
1917
1917
|
);
|
|
1918
1918
|
}
|
|
1919
1919
|
if (executableDir) {
|
|
1920
1920
|
let localFile = findFile(executableDir, executableFile);
|
|
1921
1921
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1922
|
-
const legacyName =
|
|
1922
|
+
const legacyName = path4.basename(
|
|
1923
1923
|
this._scriptPath,
|
|
1924
|
-
|
|
1924
|
+
path4.extname(this._scriptPath)
|
|
1925
1925
|
);
|
|
1926
1926
|
if (legacyName !== this._name) {
|
|
1927
1927
|
localFile = findFile(
|
|
@@ -1932,7 +1932,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1932
1932
|
}
|
|
1933
1933
|
executableFile = localFile || executableFile;
|
|
1934
1934
|
}
|
|
1935
|
-
launchWithNode = sourceExt.includes(
|
|
1935
|
+
launchWithNode = sourceExt.includes(path4.extname(executableFile));
|
|
1936
1936
|
let proc;
|
|
1937
1937
|
if (process2.platform !== "win32") {
|
|
1938
1938
|
if (launchWithNode) {
|
|
@@ -2772,7 +2772,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2772
2772
|
* @return {Command}
|
|
2773
2773
|
*/
|
|
2774
2774
|
nameFromFilename(filename) {
|
|
2775
|
-
this._name =
|
|
2775
|
+
this._name = path4.basename(filename, path4.extname(filename));
|
|
2776
2776
|
return this;
|
|
2777
2777
|
}
|
|
2778
2778
|
/**
|
|
@@ -2786,9 +2786,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2786
2786
|
* @param {string} [path]
|
|
2787
2787
|
* @return {(string|null|Command)}
|
|
2788
2788
|
*/
|
|
2789
|
-
executableDir(
|
|
2790
|
-
if (
|
|
2791
|
-
this._executableDir =
|
|
2789
|
+
executableDir(path5) {
|
|
2790
|
+
if (path5 === void 0) return this._executableDir;
|
|
2791
|
+
this._executableDir = path5;
|
|
2792
2792
|
return this;
|
|
2793
2793
|
}
|
|
2794
2794
|
/**
|
|
@@ -3368,6 +3368,71 @@ var FlightDeskAPI = class _FlightDeskAPI {
|
|
|
3368
3368
|
const result = await this.graphql(query, { taskId });
|
|
3369
3369
|
return result.userTaskPrompts;
|
|
3370
3370
|
}
|
|
3371
|
+
// ============================================================================
|
|
3372
|
+
// Preview Environment Operations
|
|
3373
|
+
// ============================================================================
|
|
3374
|
+
async getTaskInstance(taskId) {
|
|
3375
|
+
const query = `
|
|
3376
|
+
query GetTaskInstance($taskId: String!) {
|
|
3377
|
+
userTaskInstance(taskId: $taskId) {
|
|
3378
|
+
id
|
|
3379
|
+
taskId
|
|
3380
|
+
workerId
|
|
3381
|
+
containerId
|
|
3382
|
+
previewUrl
|
|
3383
|
+
sshHost
|
|
3384
|
+
sshPort
|
|
3385
|
+
sshUser
|
|
3386
|
+
status
|
|
3387
|
+
lastActivityAt
|
|
3388
|
+
suspendedAt
|
|
3389
|
+
createdAt
|
|
3390
|
+
updatedAt
|
|
3391
|
+
sshConnectionString
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
`;
|
|
3395
|
+
const result = await this.graphql(query, { taskId });
|
|
3396
|
+
return result.userTaskInstance;
|
|
3397
|
+
}
|
|
3398
|
+
async getInstanceLogs(taskId, lines = 100) {
|
|
3399
|
+
const query = `
|
|
3400
|
+
query GetInstanceLogs($taskId: String!, $lines: Float) {
|
|
3401
|
+
userInstanceLogs(taskId: $taskId, lines: $lines) {
|
|
3402
|
+
logs
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
`;
|
|
3406
|
+
const result = await this.graphql(query, { taskId, lines });
|
|
3407
|
+
return result.userInstanceLogs;
|
|
3408
|
+
}
|
|
3409
|
+
async restartInstance(taskId) {
|
|
3410
|
+
const query = `
|
|
3411
|
+
mutation RestartInstance($taskId: String!) {
|
|
3412
|
+
userRestartInstance(taskId: $taskId)
|
|
3413
|
+
}
|
|
3414
|
+
`;
|
|
3415
|
+
const result = await this.graphql(query, { taskId });
|
|
3416
|
+
return result.userRestartInstance;
|
|
3417
|
+
}
|
|
3418
|
+
async resumeInstance(taskId) {
|
|
3419
|
+
const query = `
|
|
3420
|
+
mutation ResumeInstance($taskId: String!) {
|
|
3421
|
+
userResumeInstance(taskId: $taskId)
|
|
3422
|
+
}
|
|
3423
|
+
`;
|
|
3424
|
+
const result = await this.graphql(query, { taskId });
|
|
3425
|
+
return result.userResumeInstance;
|
|
3426
|
+
}
|
|
3427
|
+
async tearDownInstance(taskId) {
|
|
3428
|
+
const query = `
|
|
3429
|
+
mutation TearDownInstance($taskId: String!) {
|
|
3430
|
+
userTearDownInstance(taskId: $taskId)
|
|
3431
|
+
}
|
|
3432
|
+
`;
|
|
3433
|
+
const result = await this.graphql(query, { taskId });
|
|
3434
|
+
return result.userTearDownInstance;
|
|
3435
|
+
}
|
|
3371
3436
|
};
|
|
3372
3437
|
async function fetchUserInfo(apiKey, apiUrl = "https://api.flightdesk.dev") {
|
|
3373
3438
|
const response = await fetch(`${apiUrl}/graphql`, {
|
|
@@ -3735,11 +3800,54 @@ async function authCommand() {
|
|
|
3735
3800
|
}
|
|
3736
3801
|
}
|
|
3737
3802
|
|
|
3803
|
+
// apps/cli/src/lib/git.ts
|
|
3804
|
+
var import_node_child_process = require("node:child_process");
|
|
3805
|
+
function detectGitRepo() {
|
|
3806
|
+
try {
|
|
3807
|
+
const remoteUrl = (0, import_node_child_process.execSync)("git remote get-url origin", {
|
|
3808
|
+
encoding: "utf-8",
|
|
3809
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3810
|
+
}).trim();
|
|
3811
|
+
const repoFullName = parseGitRemoteUrl(remoteUrl);
|
|
3812
|
+
if (!repoFullName) {
|
|
3813
|
+
return null;
|
|
3814
|
+
}
|
|
3815
|
+
const branch = (0, import_node_child_process.execSync)("git rev-parse --abbrev-ref HEAD", {
|
|
3816
|
+
encoding: "utf-8",
|
|
3817
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3818
|
+
}).trim();
|
|
3819
|
+
return {
|
|
3820
|
+
remote: repoFullName,
|
|
3821
|
+
branch,
|
|
3822
|
+
remoteUrl
|
|
3823
|
+
};
|
|
3824
|
+
} catch {
|
|
3825
|
+
return null;
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
function parseGitRemoteUrl(remoteUrl) {
|
|
3829
|
+
const sshPattern = /git@github\.com:([^/]+\/[^/]+?)(?:\.git)?$/;
|
|
3830
|
+
const sshMatch = sshPattern.exec(remoteUrl);
|
|
3831
|
+
if (sshMatch) {
|
|
3832
|
+
return sshMatch[1];
|
|
3833
|
+
}
|
|
3834
|
+
const httpsPattern = /https:\/\/github\.com\/([^/]+\/[^/]+?)(?:\.git)?$/;
|
|
3835
|
+
const httpsMatch = httpsPattern.exec(remoteUrl);
|
|
3836
|
+
if (httpsMatch) {
|
|
3837
|
+
return httpsMatch[1];
|
|
3838
|
+
}
|
|
3839
|
+
return null;
|
|
3840
|
+
}
|
|
3841
|
+
|
|
3738
3842
|
// apps/cli/src/commands/register.ts
|
|
3739
|
-
async function registerCommand(
|
|
3843
|
+
async function registerCommand(taskId, options) {
|
|
3740
3844
|
const { config, org: org2 } = requireActiveOrg();
|
|
3741
3845
|
const api = FlightDeskAPI.fromConfig(config, org2);
|
|
3742
3846
|
try {
|
|
3847
|
+
let projectId = options.project;
|
|
3848
|
+
if (!projectId) {
|
|
3849
|
+
projectId = await autoDetectProject(api, org2);
|
|
3850
|
+
}
|
|
3743
3851
|
let viewUrl = options.viewUrl;
|
|
3744
3852
|
let teleportId = options.teleportId;
|
|
3745
3853
|
if (!process.stdin.isTTY) {
|
|
@@ -3810,6 +3918,53 @@ function parseClaudeOutput(output) {
|
|
|
3810
3918
|
}
|
|
3811
3919
|
return result;
|
|
3812
3920
|
}
|
|
3921
|
+
async function autoDetectProject(api, activeOrg) {
|
|
3922
|
+
const repoInfo = detectGitRepo();
|
|
3923
|
+
if (!repoInfo) {
|
|
3924
|
+
console.error("\u274C Not in a git repository. Please provide a project ID.");
|
|
3925
|
+
console.error(" Usage: flightdesk register --project <project-id> [task-id]");
|
|
3926
|
+
process.exit(1);
|
|
3927
|
+
}
|
|
3928
|
+
console.log(`\u{1F50D} Detecting project from repository: ${repoInfo.remote}`);
|
|
3929
|
+
const mappedOrg = getOrganizationByRepo(repoInfo.remote);
|
|
3930
|
+
if (!mappedOrg) {
|
|
3931
|
+
console.error(`\u274C Repository "${repoInfo.remote}" is not mapped to any organization.`);
|
|
3932
|
+
console.error(" Run: flightdesk sync");
|
|
3933
|
+
console.error(" Or provide a project ID: flightdesk register --project <project-id> [task-id]");
|
|
3934
|
+
process.exit(1);
|
|
3935
|
+
}
|
|
3936
|
+
if (mappedOrg.id !== activeOrg.id) {
|
|
3937
|
+
console.error(`\u274C Repository "${repoInfo.remote}" is mapped to organization "${mappedOrg.name}",`);
|
|
3938
|
+
console.error(` but your active organization is "${activeOrg.name}".`);
|
|
3939
|
+
console.error(" Switch with: flightdesk org switch " + mappedOrg.name);
|
|
3940
|
+
console.error(" Or provide a project ID: flightdesk register --project <project-id> [task-id]");
|
|
3941
|
+
process.exit(1);
|
|
3942
|
+
}
|
|
3943
|
+
const projects = await api.listProjects();
|
|
3944
|
+
const matchingProjects = projects.filter(
|
|
3945
|
+
(p) => p.githubRepo?.toLowerCase() === repoInfo.remote.toLowerCase()
|
|
3946
|
+
);
|
|
3947
|
+
if (matchingProjects.length === 0) {
|
|
3948
|
+
console.error(`\u274C No FlightDesk project found for repository "${repoInfo.remote}".`);
|
|
3949
|
+
console.error(" Available projects in this organization:");
|
|
3950
|
+
for (const p of projects) {
|
|
3951
|
+
console.error(` - ${p.name} (${p.githubRepo || "no repo"}) [${p.id}]`);
|
|
3952
|
+
}
|
|
3953
|
+
console.error("\n Create a project or provide a project ID explicitly.");
|
|
3954
|
+
process.exit(1);
|
|
3955
|
+
}
|
|
3956
|
+
if (matchingProjects.length > 1) {
|
|
3957
|
+
console.error(`\u274C Multiple projects found for repository "${repoInfo.remote}":`);
|
|
3958
|
+
for (const p of matchingProjects) {
|
|
3959
|
+
console.error(` - ${p.name} [${p.id}]`);
|
|
3960
|
+
}
|
|
3961
|
+
console.error("\n Please specify the project ID explicitly.");
|
|
3962
|
+
process.exit(1);
|
|
3963
|
+
}
|
|
3964
|
+
const project2 = matchingProjects[0];
|
|
3965
|
+
console.log(`\u2705 Found project: ${project2.name} (${project2.id})`);
|
|
3966
|
+
return project2.id;
|
|
3967
|
+
}
|
|
3813
3968
|
|
|
3814
3969
|
// apps/cli/src/commands/status.ts
|
|
3815
3970
|
async function statusCommand(options) {
|
|
@@ -4875,9 +5030,277 @@ async function listProjects(api) {
|
|
|
4875
5030
|
${projects.length} project(s)`);
|
|
4876
5031
|
}
|
|
4877
5032
|
|
|
5033
|
+
// apps/cli/src/commands/preview.ts
|
|
5034
|
+
var import_node_child_process2 = require("node:child_process");
|
|
5035
|
+
var path3 = __toESM(require("node:path"));
|
|
5036
|
+
var os3 = __toESM(require("node:os"));
|
|
5037
|
+
var fs4 = __toESM(require("node:fs"));
|
|
5038
|
+
function isValidContainerId(id) {
|
|
5039
|
+
return /^[a-fA-F0-9]+$/.test(id) && id.length >= 12 && id.length <= 64;
|
|
5040
|
+
}
|
|
5041
|
+
function validateSSHParams(instance) {
|
|
5042
|
+
if (instance.containerId && !isValidContainerId(instance.containerId)) {
|
|
5043
|
+
throw new Error(`Invalid container ID format: ${instance.containerId}`);
|
|
5044
|
+
}
|
|
5045
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(instance.sshUser)) {
|
|
5046
|
+
throw new Error(`Invalid SSH user format: ${instance.sshUser}`);
|
|
5047
|
+
}
|
|
5048
|
+
if (!/^[a-zA-Z0-9.-]+$/.test(instance.sshHost)) {
|
|
5049
|
+
throw new Error(`Invalid SSH host format: ${instance.sshHost}`);
|
|
5050
|
+
}
|
|
5051
|
+
if (!Number.isInteger(instance.sshPort) || instance.sshPort < 1 || instance.sshPort > 65535) {
|
|
5052
|
+
throw new Error(`Invalid SSH port: ${instance.sshPort}`);
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
async function previewCommand(action, options) {
|
|
5056
|
+
const { config, org: org2 } = requireActiveOrg();
|
|
5057
|
+
const api = FlightDeskAPI.fromConfig(config, org2);
|
|
5058
|
+
try {
|
|
5059
|
+
switch (action) {
|
|
5060
|
+
case "status":
|
|
5061
|
+
await handleStatus2(api, options);
|
|
5062
|
+
break;
|
|
5063
|
+
case "logs":
|
|
5064
|
+
await handleLogs(api, options);
|
|
5065
|
+
break;
|
|
5066
|
+
case "mount":
|
|
5067
|
+
await handleMount(api, options);
|
|
5068
|
+
break;
|
|
5069
|
+
case "unmount":
|
|
5070
|
+
await handleUnmount(api, options);
|
|
5071
|
+
break;
|
|
5072
|
+
case "restart":
|
|
5073
|
+
await handleRestart(api, options);
|
|
5074
|
+
break;
|
|
5075
|
+
case "resume":
|
|
5076
|
+
await handleResume(api, options);
|
|
5077
|
+
break;
|
|
5078
|
+
case "teardown":
|
|
5079
|
+
await handleTeardown(api, options);
|
|
5080
|
+
break;
|
|
5081
|
+
}
|
|
5082
|
+
} catch (error) {
|
|
5083
|
+
console.error(`Error: ${error}`);
|
|
5084
|
+
process.exit(1);
|
|
5085
|
+
}
|
|
5086
|
+
}
|
|
5087
|
+
function getStatusEmoji3(status) {
|
|
5088
|
+
const statusEmojis = {
|
|
5089
|
+
STARTING: "\u{1F504}",
|
|
5090
|
+
READY: "\u2705",
|
|
5091
|
+
SUSPENDED: "\u{1F4A4}",
|
|
5092
|
+
RESTARTING: "\u{1F504}",
|
|
5093
|
+
TEARDOWN: "\u{1F5D1}\uFE0F",
|
|
5094
|
+
ERROR: "\u274C"
|
|
5095
|
+
};
|
|
5096
|
+
return statusEmojis[status] || "\u2753";
|
|
5097
|
+
}
|
|
5098
|
+
async function handleStatus2(api, options) {
|
|
5099
|
+
const instance = await api.getTaskInstance(options.taskId);
|
|
5100
|
+
if (!instance) {
|
|
5101
|
+
console.log("\n\u26A0\uFE0F No preview environment found for this task");
|
|
5102
|
+
console.log(" The preview environment may not have been created yet,");
|
|
5103
|
+
console.log(" or the PR may be closed.");
|
|
5104
|
+
return;
|
|
5105
|
+
}
|
|
5106
|
+
const emoji = getStatusEmoji3(instance.status);
|
|
5107
|
+
console.log("\n\u{1F4E6} Preview Environment");
|
|
5108
|
+
console.log("\u2500".repeat(50));
|
|
5109
|
+
console.log(`${emoji} Status: ${instance.status}`);
|
|
5110
|
+
console.log(` ID: ${instance.id.substring(0, 8)}`);
|
|
5111
|
+
console.log(` Preview URL: ${instance.previewUrl}`);
|
|
5112
|
+
console.log(` SSH: ${instance.sshConnectionString}`);
|
|
5113
|
+
console.log(` Container: ${instance.containerId.substring(0, 12)}`);
|
|
5114
|
+
if (instance.lastActivityAt) {
|
|
5115
|
+
const lastActivity = new Date(instance.lastActivityAt);
|
|
5116
|
+
const minutesAgo = Math.round((Date.now() - lastActivity.getTime()) / 6e4);
|
|
5117
|
+
console.log(` Last Activity: ${minutesAgo} minutes ago`);
|
|
5118
|
+
}
|
|
5119
|
+
if (instance.suspendedAt) {
|
|
5120
|
+
const suspended = new Date(instance.suspendedAt);
|
|
5121
|
+
console.log(` Suspended At: ${suspended.toLocaleString()}`);
|
|
5122
|
+
}
|
|
5123
|
+
console.log(` Created: ${new Date(instance.createdAt).toLocaleString()}`);
|
|
5124
|
+
console.log("");
|
|
5125
|
+
}
|
|
5126
|
+
async function handleLogs(api, options) {
|
|
5127
|
+
const lines = options.lines || 100;
|
|
5128
|
+
if (options.follow) {
|
|
5129
|
+
const instance = await api.getTaskInstance(options.taskId);
|
|
5130
|
+
if (!instance) {
|
|
5131
|
+
console.error("No preview environment found for this task");
|
|
5132
|
+
process.exit(1);
|
|
5133
|
+
}
|
|
5134
|
+
console.log(`\u{1F4CB} Streaming logs from ${instance.previewUrl}...
|
|
5135
|
+
`);
|
|
5136
|
+
console.log(`(Press Ctrl+C to stop)
|
|
5137
|
+
`);
|
|
5138
|
+
validateSSHParams(instance);
|
|
5139
|
+
const sshCommand = `docker logs -f ${instance.containerId}`;
|
|
5140
|
+
const ssh = (0, import_node_child_process2.spawn)("ssh", [
|
|
5141
|
+
"-o",
|
|
5142
|
+
"StrictHostKeyChecking=no",
|
|
5143
|
+
"-o",
|
|
5144
|
+
"UserKnownHostsFile=/dev/null",
|
|
5145
|
+
"-p",
|
|
5146
|
+
String(instance.sshPort),
|
|
5147
|
+
`${instance.sshUser}@${instance.sshHost}`,
|
|
5148
|
+
sshCommand
|
|
5149
|
+
]);
|
|
5150
|
+
ssh.stdout.pipe(process.stdout);
|
|
5151
|
+
ssh.stderr.pipe(process.stderr);
|
|
5152
|
+
ssh.on("close", (code) => {
|
|
5153
|
+
process.exit(code || 0);
|
|
5154
|
+
});
|
|
5155
|
+
process.on("SIGINT", () => {
|
|
5156
|
+
ssh.kill();
|
|
5157
|
+
process.exit(0);
|
|
5158
|
+
});
|
|
5159
|
+
return;
|
|
5160
|
+
}
|
|
5161
|
+
const result = await api.getInstanceLogs(options.taskId, lines);
|
|
5162
|
+
if (!result.logs) {
|
|
5163
|
+
console.log("No logs available");
|
|
5164
|
+
return;
|
|
5165
|
+
}
|
|
5166
|
+
console.log(result.logs);
|
|
5167
|
+
}
|
|
5168
|
+
async function handleMount(api, options) {
|
|
5169
|
+
const instance = await api.getTaskInstance(options.taskId);
|
|
5170
|
+
if (!instance) {
|
|
5171
|
+
console.error("No preview environment found for this task");
|
|
5172
|
+
process.exit(1);
|
|
5173
|
+
}
|
|
5174
|
+
if (instance.status === "SUSPENDED") {
|
|
5175
|
+
console.log("\u23F8\uFE0F Instance is suspended. Resuming...");
|
|
5176
|
+
await api.resumeInstance(options.taskId);
|
|
5177
|
+
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
5178
|
+
}
|
|
5179
|
+
try {
|
|
5180
|
+
(0, import_node_child_process2.execSync)("which sshfs", { stdio: "ignore" });
|
|
5181
|
+
} catch {
|
|
5182
|
+
console.error("\u274C sshfs is not installed.");
|
|
5183
|
+
console.error("");
|
|
5184
|
+
console.error("Install it with:");
|
|
5185
|
+
if (process.platform === "darwin") {
|
|
5186
|
+
console.error(" brew install macfuse sshfs");
|
|
5187
|
+
} else if (process.platform === "linux") {
|
|
5188
|
+
console.error(" sudo apt install sshfs");
|
|
5189
|
+
} else {
|
|
5190
|
+
console.error(" SSHFS is not supported on this platform");
|
|
5191
|
+
}
|
|
5192
|
+
process.exit(1);
|
|
5193
|
+
}
|
|
5194
|
+
const rawTaskIdPrefix = options.taskId.substring(0, 8);
|
|
5195
|
+
const safeTaskId = path3.basename(rawTaskIdPrefix).replaceAll(/[^a-zA-Z0-9_-]/g, "") || "task";
|
|
5196
|
+
const mountDir = options.directory || path3.join(os3.homedir(), "flightdesk-mounts", safeTaskId);
|
|
5197
|
+
if (!fs4.existsSync(mountDir)) {
|
|
5198
|
+
fs4.mkdirSync(mountDir, { recursive: true });
|
|
5199
|
+
}
|
|
5200
|
+
try {
|
|
5201
|
+
const mounted = (0, import_node_child_process2.execSync)("mount", { encoding: "utf8" });
|
|
5202
|
+
if (mounted.includes(mountDir)) {
|
|
5203
|
+
console.log(`\u{1F4C1} Already mounted at ${mountDir}`);
|
|
5204
|
+
return;
|
|
5205
|
+
}
|
|
5206
|
+
} catch {
|
|
5207
|
+
}
|
|
5208
|
+
console.log(`\u{1F4C1} Mounting preview environment to ${mountDir}...`);
|
|
5209
|
+
validateSSHParams(instance);
|
|
5210
|
+
const sshfsArgs = [
|
|
5211
|
+
"-o",
|
|
5212
|
+
"StrictHostKeyChecking=no",
|
|
5213
|
+
"-o",
|
|
5214
|
+
"UserKnownHostsFile=/dev/null",
|
|
5215
|
+
"-o",
|
|
5216
|
+
"reconnect",
|
|
5217
|
+
"-o",
|
|
5218
|
+
"ServerAliveInterval=15",
|
|
5219
|
+
"-o",
|
|
5220
|
+
"ServerAliveCountMax=3",
|
|
5221
|
+
"-p",
|
|
5222
|
+
String(instance.sshPort),
|
|
5223
|
+
`${instance.sshUser}@${instance.sshHost}:/app`,
|
|
5224
|
+
mountDir
|
|
5225
|
+
];
|
|
5226
|
+
try {
|
|
5227
|
+
const result = (0, import_node_child_process2.spawnSync)("sshfs", sshfsArgs, { stdio: "inherit" });
|
|
5228
|
+
if (result.status !== 0) {
|
|
5229
|
+
throw new Error(`sshfs exited with code ${result.status}`);
|
|
5230
|
+
}
|
|
5231
|
+
console.log("");
|
|
5232
|
+
console.log("\u2705 Mounted successfully!");
|
|
5233
|
+
console.log(` Location: ${mountDir}`);
|
|
5234
|
+
console.log("");
|
|
5235
|
+
console.log(" To unmount:");
|
|
5236
|
+
console.log(` fd preview unmount ${options.taskId}`);
|
|
5237
|
+
console.log("");
|
|
5238
|
+
console.log(" Or manually:");
|
|
5239
|
+
if (process.platform === "darwin") {
|
|
5240
|
+
console.log(` umount ${mountDir}`);
|
|
5241
|
+
} else {
|
|
5242
|
+
console.log(` fusermount -u ${mountDir}`);
|
|
5243
|
+
}
|
|
5244
|
+
} catch (error) {
|
|
5245
|
+
console.error(`\u274C Mount failed: ${error}`);
|
|
5246
|
+
process.exit(1);
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
5249
|
+
async function handleUnmount(_api, options) {
|
|
5250
|
+
const rawTaskIdPrefix = options.taskId.substring(0, 8);
|
|
5251
|
+
const safeTaskId = path3.basename(rawTaskIdPrefix).replaceAll(/[^a-zA-Z0-9_-]/g, "") || "task";
|
|
5252
|
+
const mountDir = path3.join(os3.homedir(), "flightdesk-mounts", safeTaskId);
|
|
5253
|
+
if (!fs4.existsSync(mountDir)) {
|
|
5254
|
+
console.log("Mount directory does not exist");
|
|
5255
|
+
return;
|
|
5256
|
+
}
|
|
5257
|
+
console.log(`\u{1F4C1} Unmounting ${mountDir}...`);
|
|
5258
|
+
try {
|
|
5259
|
+
let result;
|
|
5260
|
+
if (process.platform === "darwin") {
|
|
5261
|
+
result = (0, import_node_child_process2.spawnSync)("umount", [mountDir], { stdio: "inherit" });
|
|
5262
|
+
} else {
|
|
5263
|
+
result = (0, import_node_child_process2.spawnSync)("fusermount", ["-u", mountDir], { stdio: "inherit" });
|
|
5264
|
+
}
|
|
5265
|
+
if (result.status !== 0) {
|
|
5266
|
+
throw new Error(`Unmount exited with code ${result.status}`);
|
|
5267
|
+
}
|
|
5268
|
+
console.log("\u2705 Unmounted successfully");
|
|
5269
|
+
try {
|
|
5270
|
+
fs4.rmSync(mountDir, { recursive: true, force: true });
|
|
5271
|
+
} catch {
|
|
5272
|
+
}
|
|
5273
|
+
} catch (error) {
|
|
5274
|
+
console.error(`\u274C Unmount failed: ${error}`);
|
|
5275
|
+
console.error("");
|
|
5276
|
+
console.error("Try force unmounting:");
|
|
5277
|
+
if (process.platform === "darwin") {
|
|
5278
|
+
console.error(` diskutil unmount force ${mountDir}`);
|
|
5279
|
+
} else {
|
|
5280
|
+
console.error(` fusermount -uz ${mountDir}`);
|
|
5281
|
+
}
|
|
5282
|
+
process.exit(1);
|
|
5283
|
+
}
|
|
5284
|
+
}
|
|
5285
|
+
async function handleRestart(api, options) {
|
|
5286
|
+
console.log("\u{1F504} Restarting preview environment...");
|
|
5287
|
+
await api.restartInstance(options.taskId);
|
|
5288
|
+
console.log("\u2705 Restart initiated. The preview will be available shortly.");
|
|
5289
|
+
}
|
|
5290
|
+
async function handleResume(api, options) {
|
|
5291
|
+
console.log("\u25B6\uFE0F Resuming preview environment...");
|
|
5292
|
+
await api.resumeInstance(options.taskId);
|
|
5293
|
+
console.log("\u2705 Resume initiated. The preview should be ready in about 30 seconds.");
|
|
5294
|
+
}
|
|
5295
|
+
async function handleTeardown(api, options) {
|
|
5296
|
+
console.log("\u{1F5D1}\uFE0F Tearing down preview environment...");
|
|
5297
|
+
await api.tearDownInstance(options.taskId);
|
|
5298
|
+
console.log("\u2705 Preview environment has been torn down.");
|
|
5299
|
+
}
|
|
5300
|
+
|
|
4878
5301
|
// apps/cli/src/main.ts
|
|
4879
5302
|
var program2 = new Command();
|
|
4880
|
-
program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.1
|
|
5303
|
+
program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.2.1").option("--dev", "Use local development API (localhost:3000)").option("--api <url>", "Use custom API URL");
|
|
4881
5304
|
program2.hook("preAction", () => {
|
|
4882
5305
|
const opts = program2.opts();
|
|
4883
5306
|
if (opts.api) {
|
|
@@ -4891,7 +5314,7 @@ program2.hook("preAction", () => {
|
|
|
4891
5314
|
});
|
|
4892
5315
|
program2.command("init").description("Configure FlightDesk CLI with your API credentials").action(initCommand);
|
|
4893
5316
|
program2.command("auth").description("Log in to Claude for session monitoring").action(authCommand);
|
|
4894
|
-
program2.command("register
|
|
5317
|
+
program2.command("register [task-id]").description("Register a Claude Code session with a FlightDesk task (auto-detects project from git repo)").option("-p, --project <id>", "Project ID (auto-detected from git repo if not provided)").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);
|
|
4895
5318
|
var project = program2.command("project").description("Project management commands");
|
|
4896
5319
|
project.command("list").description("List projects in the active organization").action(() => projectCommand("list", {}));
|
|
4897
5320
|
var task = program2.command("task").description("Task management commands");
|
|
@@ -4909,20 +5332,22 @@ org.command("switch <name>").description("Switch active organization").action(or
|
|
|
4909
5332
|
org.command("refresh").description("Refresh organizations from API").action(orgRefreshCommand);
|
|
4910
5333
|
program2.command("context").description("Show current repository context and mapped organization").action(contextCommand);
|
|
4911
5334
|
program2.command("sync").description("Refresh project-to-repository mappings from all organizations").action(syncCommand);
|
|
4912
|
-
program2.command("
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
console.log("This feature will be available in a future release.");
|
|
4918
|
-
});
|
|
4919
|
-
program2.command("unmount <task-id>").description("Unmount a task filesystem").action((taskId) => {
|
|
4920
|
-
console.log("\u26A0\uFE0F Unmount command requires preview environments (Phase 9)");
|
|
4921
|
-
console.log(` Task ID: ${taskId}`);
|
|
5335
|
+
var preview = program2.command("preview").description("Preview environment management");
|
|
5336
|
+
preview.command("status <task-id>").description("Show preview environment status").action((taskId) => previewCommand("status", { taskId }));
|
|
5337
|
+
preview.command("logs <task-id>").description("Get logs from preview environment").option("-n, --lines <lines>", "Number of log lines (1-10000)", "100").option("-f, --follow", "Follow log output").action((taskId, options) => {
|
|
5338
|
+
const lines = Math.min(Math.max(Number.parseInt(options.lines || "100"), 1), 1e4);
|
|
5339
|
+
previewCommand("logs", { taskId, lines, follow: options.follow });
|
|
4922
5340
|
});
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
5341
|
+
preview.command("mount <task-id>").description("Mount preview environment filesystem via SSHFS").option("-d, --directory <path>", "Custom mount directory").action((taskId, options) => previewCommand("mount", { taskId, directory: options.directory }));
|
|
5342
|
+
preview.command("unmount <task-id>").description("Unmount preview environment filesystem").action((taskId) => previewCommand("unmount", { taskId }));
|
|
5343
|
+
preview.command("restart <task-id>").description("Restart preview environment processes").action((taskId) => previewCommand("restart", { taskId }));
|
|
5344
|
+
preview.command("resume <task-id>").description("Resume a suspended preview environment").action((taskId) => previewCommand("resume", { taskId }));
|
|
5345
|
+
preview.command("teardown <task-id>").description("Tear down preview environment").action((taskId) => previewCommand("teardown", { taskId }));
|
|
5346
|
+
program2.command("mount <task-id>").description('Mount preview environment filesystem (shorthand for "preview mount")').option("-d, --directory <path>", "Custom mount directory").action((taskId, options) => previewCommand("mount", { taskId, directory: options.directory }));
|
|
5347
|
+
program2.command("unmount <task-id>").description('Unmount preview environment filesystem (shorthand for "preview unmount")').action((taskId) => previewCommand("unmount", { taskId }));
|
|
5348
|
+
program2.command("logs <task-id>").description('Get logs from preview environment (equivalent to "preview logs")').option("-n, --lines <lines>", "Number of log lines (1-10000)", "100").option("-f, --follow", "Follow log output").action((taskId, options) => {
|
|
5349
|
+
const lines = Math.min(Math.max(Number.parseInt(options.lines || "100"), 1), 1e4);
|
|
5350
|
+
previewCommand("logs", { taskId, lines, follow: options.follow });
|
|
4926
5351
|
});
|
|
4927
5352
|
program2.parse();
|
|
4928
5353
|
//# sourceMappingURL=main.js.map
|