flightdesk 0.1.14 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/main.js +332 -32
  2. package/main.js.map +4 -4
  3. 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 path3 = require("node:path");
962
- var fs4 = require("node:fs");
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 = path3.resolve(baseDir, baseName);
1895
- if (fs4.existsSync(localBin)) return localBin;
1896
- if (sourceExt.includes(path3.extname(baseName))) return void 0;
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) => fs4.existsSync(`${localBin}${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 = fs4.realpathSync(this._scriptPath);
1910
+ resolvedScriptPath = fs5.realpathSync(this._scriptPath);
1911
1911
  } catch (err) {
1912
1912
  resolvedScriptPath = this._scriptPath;
1913
1913
  }
1914
- executableDir = path3.resolve(
1915
- path3.dirname(resolvedScriptPath),
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 = path3.basename(
1922
+ const legacyName = path4.basename(
1923
1923
  this._scriptPath,
1924
- path3.extname(this._scriptPath)
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(path3.extname(executableFile));
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 = path3.basename(filename, path3.extname(filename));
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(path4) {
2790
- if (path4 === void 0) return this._executableDir;
2791
- this._executableDir = path4;
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`, {
@@ -4875,9 +4940,248 @@ async function listProjects(api) {
4875
4940
  ${projects.length} project(s)`);
4876
4941
  }
4877
4942
 
4943
+ // apps/cli/src/commands/preview.ts
4944
+ var import_child_process2 = require("child_process");
4945
+ var path3 = __toESM(require("path"));
4946
+ var os3 = __toESM(require("os"));
4947
+ var fs4 = __toESM(require("fs"));
4948
+ async function previewCommand(action, options) {
4949
+ const { config, org: org2 } = requireActiveOrg();
4950
+ const api = FlightDeskAPI.fromConfig(config, org2);
4951
+ try {
4952
+ switch (action) {
4953
+ case "status":
4954
+ await handleStatus2(api, options);
4955
+ break;
4956
+ case "logs":
4957
+ await handleLogs(api, options);
4958
+ break;
4959
+ case "mount":
4960
+ await handleMount(api, options);
4961
+ break;
4962
+ case "unmount":
4963
+ await handleUnmount(api, options);
4964
+ break;
4965
+ case "restart":
4966
+ await handleRestart(api, options);
4967
+ break;
4968
+ case "resume":
4969
+ await handleResume(api, options);
4970
+ break;
4971
+ case "teardown":
4972
+ await handleTeardown(api, options);
4973
+ break;
4974
+ }
4975
+ } catch (error) {
4976
+ console.error(`Error: ${error}`);
4977
+ process.exit(1);
4978
+ }
4979
+ }
4980
+ function getStatusEmoji3(status) {
4981
+ const statusEmojis = {
4982
+ STARTING: "\u{1F504}",
4983
+ READY: "\u2705",
4984
+ SUSPENDED: "\u{1F4A4}",
4985
+ RESTARTING: "\u{1F504}",
4986
+ TEARDOWN: "\u{1F5D1}\uFE0F",
4987
+ ERROR: "\u274C"
4988
+ };
4989
+ return statusEmojis[status] || "\u2753";
4990
+ }
4991
+ async function handleStatus2(api, options) {
4992
+ const instance = await api.getTaskInstance(options.taskId);
4993
+ if (!instance) {
4994
+ console.log("\n\u26A0\uFE0F No preview environment found for this task");
4995
+ console.log(" The preview environment may not have been created yet,");
4996
+ console.log(" or the PR may be closed.");
4997
+ return;
4998
+ }
4999
+ const emoji = getStatusEmoji3(instance.status);
5000
+ console.log("\n\u{1F4E6} Preview Environment");
5001
+ console.log("\u2500".repeat(50));
5002
+ console.log(`${emoji} Status: ${instance.status}`);
5003
+ console.log(` ID: ${instance.id.substring(0, 8)}`);
5004
+ console.log(` Preview URL: ${instance.previewUrl}`);
5005
+ console.log(` SSH: ${instance.sshConnectionString}`);
5006
+ console.log(` Container: ${instance.containerId.substring(0, 12)}`);
5007
+ if (instance.lastActivityAt) {
5008
+ const lastActivity = new Date(instance.lastActivityAt);
5009
+ const minutesAgo = Math.round((Date.now() - lastActivity.getTime()) / 6e4);
5010
+ console.log(` Last Activity: ${minutesAgo} minutes ago`);
5011
+ }
5012
+ if (instance.suspendedAt) {
5013
+ const suspended = new Date(instance.suspendedAt);
5014
+ console.log(` Suspended At: ${suspended.toLocaleString()}`);
5015
+ }
5016
+ console.log(` Created: ${new Date(instance.createdAt).toLocaleString()}`);
5017
+ console.log("");
5018
+ }
5019
+ async function handleLogs(api, options) {
5020
+ const lines = options.lines || 100;
5021
+ if (options.follow) {
5022
+ const instance = await api.getTaskInstance(options.taskId);
5023
+ if (!instance) {
5024
+ console.error("No preview environment found for this task");
5025
+ process.exit(1);
5026
+ }
5027
+ console.log(`\u{1F4CB} Streaming logs from ${instance.previewUrl}...
5028
+ `);
5029
+ console.log(`(Press Ctrl+C to stop)
5030
+ `);
5031
+ const sshCommand = `docker logs -f ${instance.containerId}`;
5032
+ const ssh = (0, import_child_process2.spawn)("ssh", [
5033
+ "-o",
5034
+ "StrictHostKeyChecking=no",
5035
+ "-o",
5036
+ "UserKnownHostsFile=/dev/null",
5037
+ "-p",
5038
+ String(instance.sshPort),
5039
+ `${instance.sshUser}@${instance.sshHost}`,
5040
+ sshCommand
5041
+ ]);
5042
+ ssh.stdout.pipe(process.stdout);
5043
+ ssh.stderr.pipe(process.stderr);
5044
+ ssh.on("close", (code) => {
5045
+ process.exit(code || 0);
5046
+ });
5047
+ process.on("SIGINT", () => {
5048
+ ssh.kill();
5049
+ process.exit(0);
5050
+ });
5051
+ return;
5052
+ }
5053
+ const result = await api.getInstanceLogs(options.taskId, lines);
5054
+ if (!result.logs) {
5055
+ console.log("No logs available");
5056
+ return;
5057
+ }
5058
+ console.log(result.logs);
5059
+ }
5060
+ async function handleMount(api, options) {
5061
+ const instance = await api.getTaskInstance(options.taskId);
5062
+ if (!instance) {
5063
+ console.error("No preview environment found for this task");
5064
+ process.exit(1);
5065
+ }
5066
+ if (instance.status === "SUSPENDED") {
5067
+ console.log("\u23F8\uFE0F Instance is suspended. Resuming...");
5068
+ await api.resumeInstance(options.taskId);
5069
+ await new Promise((resolve) => setTimeout(resolve, 3e3));
5070
+ }
5071
+ try {
5072
+ (0, import_child_process2.execSync)("which sshfs", { stdio: "ignore" });
5073
+ } catch {
5074
+ console.error("\u274C sshfs is not installed.");
5075
+ console.error("");
5076
+ console.error("Install it with:");
5077
+ if (process.platform === "darwin") {
5078
+ console.error(" brew install macfuse sshfs");
5079
+ } else if (process.platform === "linux") {
5080
+ console.error(" sudo apt install sshfs");
5081
+ } else {
5082
+ console.error(" SSHFS is not supported on this platform");
5083
+ }
5084
+ process.exit(1);
5085
+ }
5086
+ const mountDir = options.directory || path3.join(os3.homedir(), "flightdesk-mounts", options.taskId.substring(0, 8));
5087
+ if (!fs4.existsSync(mountDir)) {
5088
+ fs4.mkdirSync(mountDir, { recursive: true });
5089
+ }
5090
+ try {
5091
+ const mounted = (0, import_child_process2.execSync)("mount", { encoding: "utf8" });
5092
+ if (mounted.includes(mountDir)) {
5093
+ console.log(`\u{1F4C1} Already mounted at ${mountDir}`);
5094
+ return;
5095
+ }
5096
+ } catch {
5097
+ }
5098
+ console.log(`\u{1F4C1} Mounting preview environment to ${mountDir}...`);
5099
+ const sshfsCmd = [
5100
+ "sshfs",
5101
+ "-o",
5102
+ "StrictHostKeyChecking=no",
5103
+ "-o",
5104
+ "UserKnownHostsFile=/dev/null",
5105
+ "-o",
5106
+ "reconnect",
5107
+ "-o",
5108
+ "ServerAliveInterval=15",
5109
+ "-o",
5110
+ "ServerAliveCountMax=3",
5111
+ "-p",
5112
+ String(instance.sshPort),
5113
+ `${instance.sshUser}@${instance.sshHost}:/app`,
5114
+ mountDir
5115
+ ];
5116
+ try {
5117
+ (0, import_child_process2.execSync)(sshfsCmd.join(" "), { stdio: "inherit" });
5118
+ console.log("");
5119
+ console.log("\u2705 Mounted successfully!");
5120
+ console.log(` Location: ${mountDir}`);
5121
+ console.log("");
5122
+ console.log(" To unmount:");
5123
+ console.log(` fd preview unmount ${options.taskId}`);
5124
+ console.log("");
5125
+ console.log(" Or manually:");
5126
+ if (process.platform === "darwin") {
5127
+ console.log(` umount ${mountDir}`);
5128
+ } else {
5129
+ console.log(` fusermount -u ${mountDir}`);
5130
+ }
5131
+ } catch (error) {
5132
+ console.error(`\u274C Mount failed: ${error}`);
5133
+ process.exit(1);
5134
+ }
5135
+ }
5136
+ async function handleUnmount(_api, options) {
5137
+ const mountDir = path3.join(os3.homedir(), "flightdesk-mounts", options.taskId.substring(0, 8));
5138
+ if (!fs4.existsSync(mountDir)) {
5139
+ console.log("Mount directory does not exist");
5140
+ return;
5141
+ }
5142
+ console.log(`\u{1F4C1} Unmounting ${mountDir}...`);
5143
+ try {
5144
+ if (process.platform === "darwin") {
5145
+ (0, import_child_process2.execSync)(`umount ${mountDir}`, { stdio: "inherit" });
5146
+ } else {
5147
+ (0, import_child_process2.execSync)(`fusermount -u ${mountDir}`, { stdio: "inherit" });
5148
+ }
5149
+ console.log("\u2705 Unmounted successfully");
5150
+ try {
5151
+ fs4.rmdirSync(mountDir);
5152
+ } catch {
5153
+ }
5154
+ } catch (error) {
5155
+ console.error(`\u274C Unmount failed: ${error}`);
5156
+ console.error("");
5157
+ console.error("Try force unmounting:");
5158
+ if (process.platform === "darwin") {
5159
+ console.error(` diskutil unmount force ${mountDir}`);
5160
+ } else {
5161
+ console.error(` fusermount -uz ${mountDir}`);
5162
+ }
5163
+ process.exit(1);
5164
+ }
5165
+ }
5166
+ async function handleRestart(api, options) {
5167
+ console.log("\u{1F504} Restarting preview environment...");
5168
+ await api.restartInstance(options.taskId);
5169
+ console.log("\u2705 Restart initiated. The preview will be available shortly.");
5170
+ }
5171
+ async function handleResume(api, options) {
5172
+ console.log("\u25B6\uFE0F Resuming preview environment...");
5173
+ await api.resumeInstance(options.taskId);
5174
+ console.log("\u2705 Resume initiated. The preview should be ready in about 30 seconds.");
5175
+ }
5176
+ async function handleTeardown(api, options) {
5177
+ console.log("\u{1F5D1}\uFE0F Tearing down preview environment...");
5178
+ await api.tearDownInstance(options.taskId);
5179
+ console.log("\u2705 Preview environment has been torn down.");
5180
+ }
5181
+
4878
5182
  // apps/cli/src/main.ts
4879
5183
  var program2 = new Command();
4880
- program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.1.14").option("--dev", "Use local development API (localhost:3000)").option("--api <url>", "Use custom API URL");
5184
+ program2.name("flightdesk").description("FlightDesk CLI - AI task management for Claude Code sessions").version("0.2.0").option("--dev", "Use local development API (localhost:3000)").option("--api <url>", "Use custom API URL");
4881
5185
  program2.hook("preAction", () => {
4882
5186
  const opts = program2.opts();
4883
5187
  if (opts.api) {
@@ -4909,20 +5213,16 @@ org.command("switch <name>").description("Switch active organization").action(or
4909
5213
  org.command("refresh").description("Refresh organizations from API").action(orgRefreshCommand);
4910
5214
  program2.command("context").description("Show current repository context and mapped organization").action(contextCommand);
4911
5215
  program2.command("sync").description("Refresh project-to-repository mappings from all organizations").action(syncCommand);
4912
- program2.command("mount <task-id>").description("Mount preview environment filesystem via SSHFS").option("--vscode", "Open in VS Code after mounting").action((taskId) => {
4913
- console.log("\u26A0\uFE0F Mount command requires preview environments (Phase 9)");
4914
- console.log(` Task ID: ${taskId}`);
4915
- console.log("");
4916
- console.log("Preview environments are not yet implemented.");
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}`);
4922
- });
4923
- program2.command("logs <task-id>").description("Stream logs from a preview environment").option("-f, --follow", "Follow log output").option("--process <name>", "Filter by process name").action((taskId) => {
4924
- console.log("\u26A0\uFE0F Logs command requires preview environments (Phase 9)");
4925
- console.log(` Task ID: ${taskId}`);
4926
- });
5216
+ var preview = program2.command("preview").description("Preview environment management");
5217
+ preview.command("status <task-id>").description("Show preview environment status").action((taskId) => previewCommand("status", { taskId }));
5218
+ preview.command("logs <task-id>").description("Get logs from preview environment").option("-n, --lines <lines>", "Number of log lines", "100").option("-f, --follow", "Follow log output").action((taskId, options) => previewCommand("logs", { taskId, lines: parseInt(options.lines || "100"), follow: options.follow }));
5219
+ 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 }));
5220
+ preview.command("unmount <task-id>").description("Unmount preview environment filesystem").action((taskId) => previewCommand("unmount", { taskId }));
5221
+ preview.command("restart <task-id>").description("Restart preview environment processes").action((taskId) => previewCommand("restart", { taskId }));
5222
+ preview.command("resume <task-id>").description("Resume a suspended preview environment").action((taskId) => previewCommand("resume", { taskId }));
5223
+ preview.command("teardown <task-id>").description("Tear down preview environment").action((taskId) => previewCommand("teardown", { taskId }));
5224
+ 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 }));
5225
+ program2.command("unmount <task-id>").description('Unmount preview environment filesystem (shorthand for "preview unmount")').action((taskId) => previewCommand("unmount", { taskId }));
5226
+ program2.command("logs <task-id>").description('Get logs from preview environment (shorthand for "preview logs")').option("-n, --lines <lines>", "Number of log lines", "100").option("-f, --follow", "Follow log output").action((taskId, options) => previewCommand("logs", { taskId, lines: parseInt(options.lines || "100"), follow: options.follow }));
4927
5227
  program2.parse();
4928
5228
  //# sourceMappingURL=main.js.map