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.
Files changed (3) hide show
  1. package/main.js +457 -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`, {
@@ -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(projectId, taskId, options) {
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.14").option("--dev", "Use local development API (localhost:3000)").option("--api <url>", "Use custom API URL");
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 <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);
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("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}`);
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
- 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}`);
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