@treeport/treeport 0.7.0 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Ct as projectsResponseSchema, Dt as terminalCaptureResponseSchema, G as treeportRpcClientLayer, Gt as webPanelDefinitionsResponseSchema, K as TreeportRpcs, Lt as treeContextResponseSchema, Nt as terminalResponseSchema, Ot as terminalObservationResponseSchema, Pt as terminatedTerminalsResponseSchema, St as projectResponseSchema, Tt as removePreviewResponseSchema, U as decodeUnknownOrNull, V as webPanelInputSchema, _t as packageOperationResponseSchema, bt as packageReloadResponseSchema, dt as openBrowserPanelResponseSchema, et as browserAgentResponseSchema, ft as openWebPanelResponseSchema, gt as packageListingResponseSchema, mt as operationResponseSchema, nt as browserInstallStatusSchema, o as apiErrorBodySchema, t as TERMINAL_CAPTURE_MAX_LINES, tt as browserInstallResponseSchema, ut as okResponseSchema, vt as packageOperationsResponseSchema, yt as packageProjectResponseSchema } from "../../dist-BsLn2Gbc.js";
3
- import { A as tailscaleRemoteStatus, C as daemonUp, D as resolveLocalApiUrl, E as readDaemonLogs, N as parseDurationMs, O as resolvePackagePath, S as daemonStatus, T as enableTailscaleRemote, _ as serviceStart, b as daemonDown, c as runLocalUpdate, d as serviceApply, f as serviceDisable, g as serviceRun, h as serviceInstalled, j as treeportVersion, k as runDoctor, m as serviceEnable, p as serviceDoctorCheck, r as formatLocalUpdateError, t as LocalUpdateError, u as readServiceLogs, v as serviceStatus, w as disableTailscaleRemote, x as daemonHealth, y as serviceStop } from "../../update-UYS2lMdD.js";
3
+ import { A as runDoctor, C as daemonStatus, D as readDaemonLogs, E as enableTailscaleRemote, F as formatServiceStatus, I as humanOutput, L as stateName, M as treeportVersion, N as formatLocalUpdateError, O as resolveLocalApiUrl, P as formatLocalUpdateResult, S as daemonHealth, T as disableTailscaleRemote, _ as serviceRun, b as serviceStop, d as readServiceLogs, f as serviceApply, g as serviceInstalled, h as serviceEnable, j as tailscaleRemoteStatus, k as resolvePackagePath, l as runLocalUpdate, m as serviceDoctorCheck, p as serviceDisable, r as confirmLocalUpdate, t as LocalUpdateError, v as serviceStart, w as daemonUp, x as daemonDown, y as serviceStatus, z as parseDurationMs } from "../../update-BYHlwpAq.js";
4
4
  import fs from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  import { Command, CommanderError } from "commander";
@@ -99,6 +99,9 @@ let writeStderr = (value) => {
99
99
  };
100
100
  let requestedExitCode = 0;
101
101
  let cliEnvironment = process.env;
102
+ let output = humanOutput();
103
+ let errorOutput = humanOutput();
104
+ let updateProgressShown = false;
102
105
  var CliError = class extends Error {
103
106
  exitCode;
104
107
  code;
@@ -119,22 +122,6 @@ async function resolveDaemonLifecycle() {
119
122
  }
120
123
  return await serviceInstalled() ? "service" : "treeport";
121
124
  }
122
- function formatServiceStatus(status) {
123
- const mode = status.mode === "headless" ? "advanced headless (starts before login)" : status.mode === "user" && status.manager === "launchd" ? "user/login (starts after login)" : status.mode === "user" ? "user service" : "not installed";
124
- const lines = [
125
- `Treeport service: ${status.state}`,
126
- `Mode: ${mode}`,
127
- `Manager: ${status.manager ?? "unsupported"}`,
128
- `Starts before login: ${status.enabledAtBoot ? "yes" : "no"}`,
129
- `Active: ${status.active ? "yes" : "no"}`,
130
- `Definition: ${status.definitionPath ?? "not installed"}`
131
- ];
132
- if (status.daemon?.state) lines.push(`PID: ${status.daemon.state.pid}`);
133
- if (status.issues.length) lines.push(...status.issues.map((issue) => `Issue: ${issue}`));
134
- if (status.administratorCommand) lines.push("Administrator action required:", status.administratorCommand, "Then run: treeport service status");
135
- else if (status.recoveryCommands.length) lines.push(`Next: ${status.recoveryCommands[0]}`);
136
- return lines.join("\n");
137
- }
138
125
  async function ensureServiceDaemon() {
139
126
  const result = await serviceStart();
140
127
  const state = result.status.daemon?.state;
@@ -474,12 +461,17 @@ function print(value, human) {
474
461
  async function main(args) {
475
462
  const argv = args[0] === "spawn" || args[0] === "terminal" && args[1] === "create" ? commandArgv(args) : void 0;
476
463
  let parserError = "";
477
- const program = new Command().name("treeport").usage("[options] [folder] [command]").description("Manage Treeport projects, trees, and terminals.").argument("[folder]", "folder or folder inside a Git repository to open").option("--json", "emit machine-readable JSON").configureOutput({
464
+ const program = new Command().name("treeport").usage("[options] [folder] [command]").description("Manage Treeport projects, trees, and terminals.").version(output.heading(await treeportVersion()), "-v, --version", "show installed CLI version").argument("[folder]", "folder or folder inside a Git repository to open").option("--json", "emit machine-readable JSON").configureOutput({
465
+ getOutHasColors: () => output.enabled,
466
+ getErrHasColors: () => errorOutput.enabled,
478
467
  writeOut: writeStdout,
479
468
  writeErr: (value) => {
480
469
  parserError += value;
481
470
  }
482
- }).showHelpAfterError().exitOverride();
471
+ }).showHelpAfterError(jsonOutput).configureHelp({
472
+ styleTitle: output.heading,
473
+ styleCommandText: output.heading
474
+ }).exitOverride();
483
475
  program.action(async (folder) => {
484
476
  if (folder === void 0) {
485
477
  writeStdout(program.helpInformation());
@@ -535,7 +527,7 @@ async function main(args) {
535
527
  if (lifecycle === "service") {
536
528
  if (options.foreground || options.host || options.port) throw new CliError("An installed service owns the listener and process mode. Run `treeport service enable` to refresh its configuration, or `treeport service disable` to return to local background mode.", 5, "DAEMON_LIFECYCLE_SERVICE");
537
529
  const result = await serviceStart();
538
- print(result, () => formatServiceStatus(result.status));
530
+ print(result, () => formatServiceStatus(result.status, output));
539
531
  if (result.administratorCommand || !result.status.healthy) requestedExitCode = 1;
540
532
  return;
541
533
  }
@@ -543,10 +535,13 @@ async function main(args) {
543
535
  const daemonOptions = {};
544
536
  if (options.host !== void 0) daemonOptions.host = options.host;
545
537
  if (port !== void 0) daemonOptions.port = port;
546
- if (options.foreground !== void 0) daemonOptions.foreground = options.foreground;
538
+ if (options.foreground !== void 0) {
539
+ daemonOptions.foreground = options.foreground;
540
+ daemonOptions.output = output;
541
+ }
547
542
  const result = await daemonUp(daemonOptions);
548
543
  if (options.foreground) return;
549
- print(result, () => `Treeport is running\n${result.apiUrl}`);
544
+ print(result, () => output.blocks(output.summary("Treeport is running", "success"), output.rows([["URL", result.apiUrl]])));
550
545
  });
551
546
  const stopCommand = program.command("stop").description("Stop the local daemon and preserve terminal sessions").option("--terminate-terminals", "terminate every Treeport-owned terminal process").option("--force", "confirm termination of all terminals").option("--json", "emit machine-readable JSON");
552
547
  stopCommand.action(async () => {
@@ -557,12 +552,12 @@ async function main(args) {
557
552
  if (options.terminateTerminals) await request("/api/admin/terminate-terminals", terminatedTerminalsResponseSchema, { method: "POST" });
558
553
  if (lifecycle === "service") {
559
554
  const result = await serviceStop();
560
- print(result, () => formatServiceStatus(result.status));
555
+ print(result, () => formatServiceStatus(result.status, output));
561
556
  if (result.administratorCommand) requestedExitCode = 1;
562
557
  return;
563
558
  }
564
559
  const result = await daemonDown();
565
- print(result, () => result.wasRunning ? "Treeport is stopped" : "Treeport is already stopped");
560
+ print(result, () => output.summary(result.wasRunning ? "Treeport is stopped" : "Treeport is already stopped", "success"));
566
561
  });
567
562
  const serviceCommand = program.command("service").description("Manage opt-in OS service supervision");
568
563
  serviceCommand.action(() => {
@@ -571,12 +566,12 @@ async function main(args) {
571
566
  const serviceEnableCommand = serviceCommand.command("enable").description("Enable automatic startup and unexpected-exit restarts").option("--headless", "use advanced macOS startup before login (requires an administrator)").option("--json", "emit machine-readable JSON");
572
567
  serviceEnableCommand.action(async () => {
573
568
  const result = await serviceEnable(serviceEnableCommand.opts().headless ? "headless" : "user");
574
- print(result, () => formatServiceStatus(result.status));
569
+ print(result, () => formatServiceStatus(result.status, output));
575
570
  if (result.status.state === "action_required") requestedExitCode = 1;
576
571
  });
577
572
  serviceCommand.command("status").description("Show OS service supervision status").option("--json", "emit machine-readable JSON").action(async () => {
578
573
  const result = await serviceStatus();
579
- print(result, () => formatServiceStatus(result));
574
+ print(result, () => formatServiceStatus(result, output));
580
575
  if (![
581
576
  "disabled",
582
577
  "healthy",
@@ -585,7 +580,7 @@ async function main(args) {
585
580
  });
586
581
  serviceCommand.command("disable").description("Stop and unregister OS service supervision").option("--json", "emit machine-readable JSON").action(async () => {
587
582
  const result = await serviceDisable();
588
- print(result, () => formatServiceStatus(result.status));
583
+ print(result, () => formatServiceStatus(result.status, output));
589
584
  if (result.administratorCommand || result.status.state !== "disabled") requestedExitCode = 1;
590
585
  });
591
586
  serviceCommand.command("run", { hidden: true }).action(async () => serviceRun());
@@ -593,7 +588,7 @@ async function main(args) {
593
588
  serviceApplyCommand.action(async () => {
594
589
  const { request: requestPath } = serviceApplyCommand.opts();
595
590
  const result = await serviceApply(requestPath);
596
- print(result, () => `Applied Treeport service ${result.operation} request.`);
591
+ print(result, () => output.summary(`Applied Treeport service ${result.operation} request.`, "success"));
597
592
  });
598
593
  const remoteCommand = program.command("remote").description("Expose Treeport privately through Tailscale Serve");
599
594
  remoteCommand.action(() => {
@@ -611,37 +606,48 @@ async function main(args) {
611
606
  if (port !== void 0) remoteOptions.port = port;
612
607
  if (serviceDaemon !== void 0) remoteOptions.daemon = serviceDaemon;
613
608
  const result = await enableTailscaleRemote(remoteOptions);
614
- print(result, () => `Treeport remote access is ${result.alreadyEnabled ? "already enabled" : "enabled"}\n${result.url}\nTailscale authenticates each remote user. Access is limited by your Tailscale policy.`);
609
+ print(result, () => output.blocks(output.summary(`Treeport remote access is ${result.alreadyEnabled ? "already enabled" : "enabled"}`, "success"), output.rows([["URL", result.url]]), "Tailscale authenticates each remote user. Access is limited by your Tailscale policy."));
615
610
  });
616
611
  remoteCommand.command("status").description("Show Tailscale remote access status").option("--json", "emit machine-readable JSON").action(async () => {
617
612
  const result = await tailscaleRemoteStatus();
618
613
  print(result, () => {
619
- if (!result.configured) return "Treeport remote access is disabled";
620
- return result.active ? `Treeport remote access is enabled\n${result.url}` : `Treeport remote access is unavailable\nExpected: ${result.url}\nThe Tailscale Serve route no longer points to Treeport.`;
614
+ if (!result.configured) return output.summary("Treeport remote access is disabled");
615
+ return output.blocks(output.summary(result.active ? "Treeport remote access is enabled" : "Treeport remote access is unavailable", result.active ? "success" : "warning"), output.rows([[result.active ? "URL" : "Expected URL", result.url ?? "Unavailable"]]), !result.active && "The Tailscale Serve route no longer points to Treeport.");
621
616
  });
622
617
  });
623
618
  remoteCommand.command("disable").description("Disable Treeport Tailscale remote access").option("--json", "emit machine-readable JSON").action(async () => {
624
619
  const result = await disableTailscaleRemote();
625
620
  print(result, () => {
626
- if (result.changedTailscale) return "Treeport remote access is disabled";
627
- return result.wasEnabled ? "Treeport remote access is disabled" : "Treeport remote access was already disabled; the current Tailscale route was left unchanged.";
621
+ if (result.changedTailscale) return output.summary("Treeport remote access is disabled", "success");
622
+ return output.summary(result.wasEnabled ? "Treeport remote access is disabled" : "Treeport remote access was already disabled; the current Tailscale route was left unchanged.", "success");
628
623
  });
629
624
  });
630
625
  program.command("status").description("Show local daemon status").option("--json", "emit machine-readable JSON").action(async () => {
631
- const status = await daemonStatus();
626
+ const [cliVersion, status] = await Promise.all([treeportVersion(), daemonStatus()]);
632
627
  const supervision = await serviceInstalled() ? await serviceStatus() : null;
628
+ const observed = supervision?.daemon ?? status;
629
+ const daemonVersion = observed.running && observed.verified ? observed.health?.version ?? null : null;
633
630
  const projectList = status.verified ? await projects() : [];
634
631
  const result = {
635
632
  ...status,
633
+ cliVersion,
634
+ daemonVersion,
636
635
  service: supervision,
637
636
  projects: projectList.length,
638
637
  worktrees: projectList.reduce((count, project) => count + project.worktrees.length, 0),
639
638
  terminals: projectList.reduce((count, project) => count + project.worktrees.reduce((worktreeCount, worktree) => worktreeCount + worktree.terminals.length, 0), 0)
640
639
  };
641
640
  print(result, () => {
642
- if (!status.state) return supervision ? formatServiceStatus(supervision) : "Treeport is stopped";
643
- if (!status.running || !status.verified) return `Treeport is unhealthy (PID ${status.state.pid})\nLogs: ${path.join(status.state.dataDir, "logs", "daemon.log")}`;
644
- return `Treeport is running\n${status.state.apiUrl}\nLifecycle: ${status.health?.daemonLifecycle}\nVersion: ${status.health?.version}\nPID: ${status.state.pid}\nProjects: ${result.projects}\nTrees: ${result.worktrees}\nTerminals: ${result.terminals}`;
641
+ const healthy = observed.running && observed.verified;
642
+ return output.blocks(output.heading("Treeport status"), output.summary(!observed.state ? "Treeport is stopped" : healthy ? "Treeport is running" : "Treeport is unhealthy", !observed.state ? "neutral" : healthy ? "success" : "failure"), output.rows([
643
+ ["CLI version", cliVersion],
644
+ ["Daemon version", daemonVersion ?? "Unavailable"],
645
+ observed.state ? ["URL", observed.state.apiUrl] : null
646
+ ]), daemonVersion !== null && daemonVersion !== cliVersion && output.summary("Version mismatch: CLI and daemon versions differ", "warning"), healthy && output.detail(output.rows([
647
+ supervision ? null : ["Lifecycle", observed.health?.daemonLifecycle === "external" ? "Externally managed" : observed.health?.daemonLifecycle === "service" ? "OS service" : "Treeport"],
648
+ supervision ? null : ["PID", observed.state.pid],
649
+ status.verified ? ["Workspace", `${result.projects} projects · ${result.worktrees} trees · ${result.terminals} terminals`] : null
650
+ ])), !healthy && observed.state !== null && output.rows([["PID", observed.state.pid], ["Logs", path.join(observed.state.dataDir, "logs", "daemon.log")]]), supervision !== null && formatServiceStatus(supervision, output));
645
651
  });
646
652
  });
647
653
  const logsCommand = program.command("logs").description("Show recent daemon logs").option("--lines <count>", "number of lines", "100");
@@ -652,7 +658,7 @@ async function main(args) {
652
658
  });
653
659
  program.command("doctor").description("Diagnose local requirements and paths").option("--json", "emit machine-readable JSON").action(async () => {
654
660
  const checks = [...await runDoctor(), await serviceDoctorCheck()];
655
- print(checks, () => checks.map((check) => `${check.ok ? "ok" : "error"}\t${check.name}\t${check.detail}`).join("\n"));
661
+ print(checks, () => output.blocks(output.heading("Treeport doctor"), output.summary(checks.every((check) => check.ok) ? "All checks passed" : "Some checks failed", checks.every((check) => check.ok) ? "success" : "failure"), ...checks.map((check) => `${output.summary(check.name, check.ok ? "success" : "failure")}\n${output.indent(check.detail.startsWith("state: ") ? stateName(check.detail.slice(7)) : check.ok ? output.detail(check.detail) : check.detail)}`)));
656
662
  if (checks.some((check) => !check.ok)) requestedExitCode = 1;
657
663
  });
658
664
  program.command("version").description("Show CLI and daemon versions").option("--json", "emit machine-readable JSON").action(async () => {
@@ -661,7 +667,7 @@ async function main(args) {
661
667
  cli,
662
668
  daemon: status.verified ? status.health?.version ?? null : null
663
669
  };
664
- print(result, () => `CLI: ${result.cli}\nDaemon: ${result.daemon ?? "not running"}`);
670
+ print(result, () => output.blocks(output.heading("Treeport version"), output.rows([["CLI", result.cli], ["Daemon", result.daemon ?? "Unavailable"]]), result.daemon !== null && result.daemon !== result.cli && output.summary("Version mismatch: CLI and daemon versions differ", "warning")));
665
671
  });
666
672
  program.command("skills").description("Print the Treeport usage guide for AI agents").action(async () => {
667
673
  const skill = await fs.readFile(await resolvePackagePath("skills", "treeport", "SKILL.md"), "utf8");
@@ -679,7 +685,7 @@ async function main(args) {
679
685
  print({
680
686
  managed: false,
681
687
  reason: "outside_treeport"
682
- }, () => "Not running in a Treeport-managed terminal.");
688
+ }, () => output.blocks(output.heading("Treeport context"), "Not running in a Treeport-managed terminal."));
683
689
  return;
684
690
  }
685
691
  const missing = [
@@ -743,7 +749,12 @@ async function main(args) {
743
749
  }).join("").split("\n");
744
750
  return ` ${key}: ${first}${rest.length > 0 ? `\n${rest.map((line) => ` ${line}`).join("\n")}` : ""}`;
745
751
  }).join("\n");
746
- return `Treeport context\n\nProject: ${context.project.name} (${context.project.id})\nTree: ${context.worktree.name} (${context.worktree.id})\nPath: ${context.worktree.path}\nContext:\n${treeContextText}\nTerminal: ${context.terminal.name} (${context.terminal.id}) — ${context.terminal.status}\nAPI: ${context.apiUrl}\nLifecycle: ${context.daemonLifecycle === "external" ? "externally managed" : context.daemonLifecycle === "service" ? "managed by the OS service" : "managed by Treeport"}`;
752
+ return output.blocks(output.heading("Treeport context"), output.rows([
753
+ ["Project", `${context.project.name} (${context.project.id})`],
754
+ ["Tree", `${context.worktree.name} (${context.worktree.id})`],
755
+ ["Path", context.worktree.path],
756
+ ["Terminal", `${context.terminal.name} (${context.terminal.id}) — ${stateName(context.terminal.status)}`]
757
+ ]), entries.length > 0 && `${output.heading("Tree context")}\n${treeContextText}`, output.detail(output.rows([["API", context.apiUrl], ["Lifecycle", context.daemonLifecycle === "external" ? "Externally managed" : context.daemonLifecycle === "service" ? "OS service" : "Treeport"]])));
747
758
  });
748
759
  });
749
760
  const browserCommand = program.command("browser").description("Manage Browser and its hosted Chromium");
@@ -868,29 +879,36 @@ async function main(args) {
868
879
  return lines.join("\n");
869
880
  });
870
881
  });
871
- const updatePackagesCommand = program.command("update").description("Update Treeport or explicitly update configured packages").argument("[source]", "one configured npm: source").option("--packages", "update every eligible configured package").option("--json", "emit machine-readable JSON");
882
+ const updatePackagesCommand = program.command("update").description("Update Treeport or explicitly update configured packages").argument("[source]", "one configured npm: source").option("--packages", "update every eligible configured package").option("-y, --yes", "approve the Treeport self-update without prompting").option("--start", "start Treeport after a self-update if it was stopped").option("--json", "emit machine-readable JSON");
872
883
  updatePackagesCommand.action(async (source) => {
873
884
  const options = updatePackagesCommand.opts();
874
885
  if (source && options.packages) throw new CliError("Specify a package source or --packages, not both.", 2);
886
+ if ((source || options.packages) && (options.yes || options.start)) throw new CliError("--yes and --start apply only to Treeport self-updates.", 2);
875
887
  if (!source && !options.packages) {
876
888
  if (await resolveDaemonLifecycle() === "external") throw new CliError("Cannot update Treeport because this daemon lifecycle is externally managed.", 5, "UPDATE_EXTERNAL_REFUSED");
877
- const selfUpdateOptions = { environment: cliEnvironment };
878
- if (!jsonOutput) selfUpdateOptions.progress = (message) => writeStderr(`${message}\n`);
879
- const result = await runLocalUpdate(selfUpdateOptions).catch((error) => {
880
- if (error instanceof LocalUpdateError) throw new CliError(jsonOutput ? error.message : formatLocalUpdateError(error.message, error.details), error.exitCode, error.code, error.details);
881
- throw error;
882
- });
883
- print(result, () => {
884
- if (result.status === "current") return `Treeport ${result.toVersion} is current`;
885
- return result.daemon.wasRunning ? `Updated Treeport from ${result.fromVersion} to ${result.toVersion} and restarted the ${result.daemon.lifecycle === "service" ? "service" : "daemon"}` : `Updated Treeport from ${result.fromVersion} to ${result.toVersion}; Treeport remains stopped`;
886
- });
889
+ const selfUpdateOptions = {
890
+ environment: cliEnvironment,
891
+ yes: options.yes ?? false,
892
+ start: options.start ?? false
893
+ };
894
+ if (!jsonOutput && process.stdin.isTTY && process.stdout.isTTY && process.stderr.isTTY) selfUpdateOptions.confirm = (preview, signal) => confirmLocalUpdate(preview, signal, process.stdin, process.stderr, errorOutput);
895
+ if (!jsonOutput) selfUpdateOptions.progress = (message) => {
896
+ if (!updateProgressShown) {
897
+ writeStderr(`${errorOutput.heading("Treeport update")}\n\n`);
898
+ updateProgressShown = true;
899
+ }
900
+ writeStderr(`${errorOutput.indent(errorOutput.summary(message, "warning"))}\n`);
901
+ };
902
+ const result = await runLocalUpdate(selfUpdateOptions);
903
+ if (updateProgressShown) writeStderr("\n");
904
+ print(result, () => formatLocalUpdateResult(result, output));
887
905
  return;
888
906
  }
889
907
  const results = (await request("/api/packages/update", packageOperationsResponseSchema, {
890
908
  method: "POST",
891
909
  body: JSON.stringify(source ? { source: await packageSource(source) } : {})
892
910
  })).results;
893
- print(results, () => results.map((result) => `${result.status}\t${result.scope}\t${result.source ?? "packages"}${result.reason ? `\t${result.reason}` : ""}`).join("\n"));
911
+ print(results, () => output.blocks(output.heading("Treeport package update"), ...results.map((result) => output.blocks(output.summary(`${stateName(result.status)} ${result.source ?? "packages"}`, result.status === "skipped" ? "warning" : "success"), output.detail(output.rows([["Scope", stateName(result.scope)]])), result.reason ? output.indent(result.reason) : null))));
894
912
  });
895
913
  const reloadCommand = program.command("reload").description("Reload package settings and resources without restarting").option("-l, --local", "reload only the registered project containing the current directory").option("--json", "emit machine-readable JSON");
896
914
  reloadCommand.action(async () => {
@@ -1114,10 +1132,13 @@ async function runCliApplication(options) {
1114
1132
  writeStdout = options.stdout ?? ((value) => process.stdout.write(value));
1115
1133
  writeStderr = options.stderr ?? ((value) => process.stderr.write(value));
1116
1134
  requestedExitCode = 0;
1135
+ updateProgressShown = false;
1136
+ output = humanOutput(environment, options.stdoutIsTTY ?? (!options.stdout && Boolean(process.stdout.isTTY)), jsonOutput);
1137
+ errorOutput = humanOutput(environment, options.stderrIsTTY ?? (!options.stderr && Boolean(process.stderr.isTTY)), jsonOutput);
1117
1138
  try {
1118
1139
  await main([...options.args]);
1119
1140
  } catch (error) {
1120
- const cliError = error instanceof CliError ? error : new CliError(error instanceof Error ? error.message : String(error), 1);
1141
+ const cliError = error instanceof CliError || error instanceof LocalUpdateError ? error : new CliError(error instanceof Error ? error.message : String(error), 1);
1121
1142
  if (jsonOutput) {
1122
1143
  const body = { error: cliError.details === void 0 ? {
1123
1144
  code: cliError.code,
@@ -1128,7 +1149,10 @@ async function runCliApplication(options) {
1128
1149
  details: cliError.details
1129
1150
  } };
1130
1151
  writeStderr(`${JSON.stringify(body)}\n`);
1131
- } else writeStderr(`${cliError.message}\n`);
1152
+ } else {
1153
+ const text = error instanceof LocalUpdateError ? formatLocalUpdateError(error.message, error.details, errorOutput, error.code === "UPDATE_CANCELLED" || error.code === "UPDATE_INTERRUPTED") : errorOutput.blocks(errorOutput.summary(cliError.code === "USAGE_ERROR" ? "Invalid command" : "Command failed", "failure"), errorOutput.indent(cliError.message.replace(/^error: /, "")), cliError.code === "USAGE_ERROR" && errorOutput.next(["treeport --help"]));
1154
+ writeStderr(`${updateProgressShown ? "\n" : ""}${text}\n`);
1155
+ }
1132
1156
  requestedExitCode = cliError.exitCode;
1133
1157
  }
1134
1158
  return requestedExitCode;
@@ -1,5 +1,5 @@
1
1
  import { $ as applicationUpdateStatusSchema, A as requestWorkspaceOpenSchema, An as parseBrowserOwnerAuth, At as terminalPresetDefinitionsQuerySchema, B as updateWebPanelPermissionGrantSchema, Bt as treeFileSearchResultSchema, C as packageUpdateSchema, Cn as browserTicketRequestSchema, Ct as projectsResponseSchema, D as reorderWorkspaceItemsSchema, Dn as parseBrowserAuth, Dt as terminalCaptureResponseSchema, E as removeWorktreeSchema, En as encodeBrowserFrame, Et as storageValueResponseSchema, F as treeContextValuesSchema, Ft as treeContextFieldListingSchema, Gt as webPanelDefinitionsResponseSchema, H as writeTreeFileSchema, Ht as uploadedFileResponseSchema, I as treeFilePathSchema, It as treeContextFieldsQuerySchema, J as parseSocketHandshake, Jt as worktreesResponseSchema, K as TreeportRpcs, Kt as webPanelResponseSchema, L as updateProjectSchema, Lt as treeContextResponseSchema, M as setWebPanelStorageSchema, Mt as terminalPresetsResponseSchema, N as terminalCaptureQuerySchema, Nt as terminalResponseSchema, O as repositoryTerminalPresetSchema, On as parseBrowserCaptureMessage, Ot as terminalObservationResponseSchema, P as treeContextFieldDefinitionSchema, Pt as terminatedTerminalsResponseSchema, Q as presenceUpdateSchema, Qt as TERMINAL_MAX_CLIENT_MESSAGE_BYTES, R as updateTerminalPresetSchema, Rt as treeFileListingSchema, S as packageRemoveSchema, Sn as browserOwnerTicketResponseSchema, St as projectResponseSchema, T as registerProjectSchema, Tn as browserUrlSchema, Tt as removePreviewResponseSchema, U as decodeUnknownOrNull, Ut as webPanelContextResponseSchema, V as webPanelInputSchema, Vt as treeFileWriteResultSchema, W as isSchemaValue, Wt as webPanelDefinitionResponseSchema, X as PRESENCE_TIMEOUT_MS, Xt as SOCKET_PATH, Y as parseSocketMessage, Yt as BROWSER_VIDEO_CAPTURE_SOURCE, Z as presenceResponseSchema, Zt as TERMINAL_CONTROLLER_GRACE_MS, _ as openBrowserPanelFromTerminalSchema, _n as BROWSER_MAX_MESSAGE_BYTES, _t as packageOperationResponseSchema, at as gitDiffImageSchema, b as packageProjectQuerySchema, bn as browserOwnerIdentitySchema, bt as packageReloadResponseSchema, c as createBrowserPanelSchema, cn as terminalInputSchema, ct as healthResponseSchema, d as createWebPanelSchema, dn as terminalResizeSchema, dt as openBrowserPanelResponseSchema, et as browserAgentResponseSchema, f as createWorktreeSchema, fn as terminalSizeSchema, ft as openWebPanelResponseSchema, g as getWebPanelStorageSchema, gt as packageListingResponseSchema, h as formatCommandLine, hn as webPanelPermissionSchema, ht as operationsResponseSchema, i as TREE_FILE_MAX_BYTES, in as parseTerminalClientEvent, it as directoryBrowseResponseSchema, j as searchTreeFilesSchema, jn as parseBrowserOwnerClientMessage, jt as terminalPresetResponseSchema, k as repositoryTerminalPresetsFileSchema, kn as parseBrowserClientMessage, kt as terminalPresetDefinitionListingSchema, l as createTerminalPresetSchema, ln as terminalOutputAckSchema, lt as listenerDiscoveryResponseSchema, m as deleteWebPanelStorageSchema, mn as gitDiffImageRequestSchema, mt as operationResponseSchema, n as TERMINAL_MAX_UPLOAD_BYTES, nt as browserInstallStatusSchema, o as apiErrorBodySchema, on as terminalBellAcknowledgementSchema, ot as gitDiffResponseSchema, p as deleteTerminalPresetSchema, pn as terminalTakeControlSchema, pt as operationQuerySchema, q as parseProductEvent, qt as worktreeResponseSchema, r as TREE_FILE_LIST_MAX_ENTRIES, rn as parseTerminalAuth, rt as deletePanelQuerySchema, s as browseDirectoryQuerySchema, sn as terminalBinarySchema, st as hasDataResponseSchema, tt as browserInstallResponseSchema, u as createTerminalSchema, un as terminalQueryAuthorityRequestSchema, ut as okResponseSchema, v as openWebPanelSchema, vn as browserAgentCommandSchema, vt as packageOperationsResponseSchema, w as readTreeFileSchema, wn as browserTicketResponseSchema, wt as recentProjectsResponseSchema, x as packageReloadSchema, xn as browserOwnerTicketRequestSchema, xt as prResponseSchema, y as packageInstallSchema, yn as browserOwnerEndpointSchema, yt as packageProjectResponseSchema, z as updateTerminalSchema, zt as treeFileSchema } from "../../dist-BsLn2Gbc.js";
2
- import { M as assertLoopbackHost, N as parseDurationMs, a as isCanonicalTreeportVersion, i as inspectLocalUpdateInstallation, l as createUpdateStartupReporter, n as compareTreeportVersions, o as readLocalUpdateProgress, r as formatLocalUpdateError, s as resolveLatestTreeportRelease, v as serviceStatus } from "../../update-UYS2lMdD.js";
2
+ import { R as assertLoopbackHost, a as inspectLocalUpdateInstallation, c as resolveLatestTreeportRelease, i as formatLocalUpdateError, n as compareTreeportVersions, o as isCanonicalTreeportVersion, s as readLocalUpdateProgress, u as createUpdateStartupReporter, y as serviceStatus, z as parseDurationMs } from "../../update-BYHlwpAq.js";
3
3
  import { a as currentTraceContext, i as terminalHostRecordSchema, n as encodeTerminalHostFrame, s as tracingLayerFromEnvironment, t as TerminalHostFrameDecoder } from "../../terminal-host-protocol-DZkQRAUF.js";
4
4
  import { createRequire } from "node:module";
5
5
  import fs from "node:fs/promises";
@@ -3562,6 +3562,7 @@ var dependencies = {
3562
3562
  "drizzle-orm": "^0.45.2",
3563
3563
  "effect": "^3.22.1",
3564
3564
  "jsonc-parser": "^3.3.1",
3565
+ "kleur": "^4.1.5",
3565
3566
  "node-pty": "1.2.0-beta.14",
3566
3567
  "playwright": "1.61.1",
3567
3568
  "vite": "8.1.5",
@@ -3891,6 +3892,7 @@ var WebPanelViteRuntime = class {
3891
3892
  this.developmentServers.clear();
3892
3893
  await Promise.all(servers.map(async ({ server }) => {
3893
3894
  await server.waitForRequestsIdle();
3895
+ await Promise.all(Object.values(server.environments).map((environment) => environment.depsOptimizer?.scanProcessing));
3894
3896
  await server.close();
3895
3897
  }));
3896
3898
  }
@@ -8080,9 +8082,17 @@ var WorktreeRemovalService = class {
8080
8082
  yield* invalidateProjectsSnapshot();
8081
8083
  return {
8082
8084
  operationId,
8083
- preview
8085
+ preview,
8086
+ skipCleanup
8084
8087
  };
8085
8088
  })).pipe(Effect.onError(() => locks.release({ worktreeIds: [worktreeId] })));
8089
+ yield* Effect.logInfo("Tree removal accepted").pipe(Effect.annotateLogs({
8090
+ operationId: accepted.operationId,
8091
+ worktreeId,
8092
+ name: accepted.preview.name,
8093
+ path: accepted.preview.path,
8094
+ skipCleanup: accepted.skipCleanup
8095
+ }));
8086
8096
  const backgroundRemoval = worktreeMutations.enqueue(worktree.projectId, executeRemove(accepted.operationId, worktreeId, accepted.preview.forceRequired)).pipe(Effect.catchAllCause((cause) => Effect.logError(`Background tree removal failed for ${accepted.operationId}: ${Cause.pretty(cause)}`)));
8087
8097
  yield* applicationFibers.fork(backgroundRemoval);
8088
8098
  yield* Effect.sync(() => events.publish("remove.started", {
@@ -8119,7 +8129,13 @@ var WorktreeRemovalService = class {
8119
8129
  UPDATE operations
8120
8130
  SET request_json=${serializeOperation(request)},updated_at=${now$1()}
8121
8131
  WHERE id=${operationId}
8122
- `))), Effect.asVoid);
8132
+ `))), Effect.zipRight(Effect.logInfo("Tree removal phase changed").pipe(Effect.annotateLogs({ phase }))), Effect.asVoid);
8133
+ yield* Effect.logInfo("Tree removal executing").pipe(Effect.annotateLogs({
8134
+ name: preview.name,
8135
+ path: preview.path,
8136
+ phase: request.phase,
8137
+ resumed: operation.status === "running"
8138
+ }));
8123
8139
  yield* Effect.promise(() => database.db.run(sql`
8124
8140
  UPDATE operations SET status='running',error=NULL,updated_at=${now$1()}
8125
8141
  WHERE id=${operationId}
@@ -8337,6 +8353,10 @@ var WorktreeRemovalService = class {
8337
8353
  updated_at=${timestamp}
8338
8354
  WHERE id=${operationId}
8339
8355
  `));
8356
+ yield* cleanupWarning ? Effect.logWarning("Tree removal completed with residual files").pipe(Effect.annotateLogs({
8357
+ warning: cleanupWarning,
8358
+ residualPath
8359
+ })) : Effect.logInfo("Tree removal completed");
8340
8360
  yield* Effect.sync(() => events.publish("remove.completed", {
8341
8361
  operationId,
8342
8362
  worktreeId: preview.worktreeId
@@ -8367,6 +8387,11 @@ var WorktreeRemovalService = class {
8367
8387
  updated_at=${now$1()}
8368
8388
  WHERE id=${operationId}
8369
8389
  `));
8390
+ yield* Effect.logWarning("Tree removal cleanup failed after Git removal").pipe(Effect.annotateLogs({
8391
+ warning,
8392
+ phase: request.phase,
8393
+ residualPath: preview.path
8394
+ }));
8370
8395
  yield* Effect.sync(() => events.publish("remove.completed", {
8371
8396
  operationId,
8372
8397
  worktreeId: preview.worktreeId
@@ -8378,6 +8403,10 @@ var WorktreeRemovalService = class {
8378
8403
  SET status='failed',result_json=NULL,error=${message},updated_at=${now$1()}
8379
8404
  WHERE id=${operationId}
8380
8405
  `));
8406
+ yield* Effect.logError("Tree removal failed; Git kept the tree").pipe(Effect.annotateLogs({
8407
+ error: message,
8408
+ phase: request.phase
8409
+ }));
8381
8410
  yield* Effect.sync(() => events.publish("remove.failed", {
8382
8411
  operationId,
8383
8412
  worktreeId: preview.worktreeId,
@@ -8385,7 +8414,10 @@ var WorktreeRemovalService = class {
8385
8414
  }));
8386
8415
  }
8387
8416
  }));
8388
- }).pipe(Effect.ensuring(Effect.flatMap(MutationLocks, (locks) => locks.release({ worktreeIds: [lockedWorktreeId] }))));
8417
+ }).pipe(Effect.ensuring(Effect.flatMap(MutationLocks, (locks) => locks.release({ worktreeIds: [lockedWorktreeId] }))), Effect.annotateLogs({
8418
+ operationId,
8419
+ worktreeId: lockedWorktreeId
8420
+ }));
8389
8421
  }
8390
8422
  resumeRemove(operationId, worktreeId, force) {
8391
8423
  return this.executeRemove(operationId, worktreeId, force);
@@ -11860,6 +11892,7 @@ var BrowserSessionManager = class {
11860
11892
  });
11861
11893
  const previousController = session.controllerId;
11862
11894
  let agentControlled = false;
11895
+ let completed = false;
11863
11896
  try {
11864
11897
  if (localOwner) {
11865
11898
  await this.updateScreencast(session, "agent");
@@ -11867,7 +11900,7 @@ var BrowserSessionManager = class {
11867
11900
  }
11868
11901
  session.controllerId = "agent";
11869
11902
  agentControlled = true;
11870
- this.broadcastState(session, "controlChanged");
11903
+ if (previousController !== "agent") this.broadcastState(session, "controlChanged");
11871
11904
  if (localOwner) result = await this.executeLocalAgentCommand(session, localOwner, input);
11872
11905
  else if (this.agentCliRunner) {
11873
11906
  const name = session.agentSessionName ?? `treeport-${panelId}-${session.generation}-${crypto.randomBytes(6).toString("hex")}`;
@@ -11893,11 +11926,12 @@ var BrowserSessionManager = class {
11893
11926
  this.queuePanelState(session, browser.state);
11894
11927
  }
11895
11928
  await this.waitForPanelState(session);
11929
+ completed = true;
11896
11930
  } catch (cause) {
11897
11931
  if (!localOwner) session.agentAttached = false;
11898
11932
  throw cause;
11899
11933
  } finally {
11900
- if (agentControlled) {
11934
+ if (agentControlled && !completed) {
11901
11935
  if (localOwner && session.localOwner === localOwner) {
11902
11936
  const nextController = previousController && previousController !== "agent" ? previousController : LOCAL_BROWSER_OWNER_CONTROLLER;
11903
11937
  if (await this.updateScreencast(session, nextController).then(() => true, () => false)) session.controllerId = nextController;
@@ -66,16 +66,21 @@ var TerminalHostSessionManager = class {
66
66
  spawnPty;
67
67
  terminateProcessTree;
68
68
  progressStaleMs;
69
+ sshAgentDiscovery;
69
70
  shellIntegrationDir;
70
71
  sessions = /* @__PURE__ */ new Map();
71
72
  pendingCleanups = /* @__PURE__ */ new Set();
72
73
  initialization = null;
73
- constructor(runtimeDir, launcherPath, spawnPty = pty.spawn, terminateProcessTree = terminatePtyProcessTree, progressStaleMs = TERMINAL_PROGRESS_STALE_MS) {
74
+ constructor(runtimeDir, launcherPath, spawnPty = pty.spawn, terminateProcessTree = terminatePtyProcessTree, progressStaleMs = TERMINAL_PROGRESS_STALE_MS, sshAgentDiscovery = {
75
+ platform: process.platform,
76
+ launchctlPath: "/bin/launchctl"
77
+ }) {
74
78
  this.runtimeDir = runtimeDir;
75
79
  this.launcherPath = launcherPath;
76
80
  this.spawnPty = spawnPty;
77
81
  this.terminateProcessTree = terminateProcessTree;
78
82
  this.progressStaleMs = progressStaleMs;
83
+ this.sshAgentDiscovery = sshAgentDiscovery;
79
84
  this.shellIntegrationDir = path.join(runtimeDir, "terminal-shell-integration");
80
85
  }
81
86
  initialize() {
@@ -110,6 +115,18 @@ var TerminalHostSessionManager = class {
110
115
  };
111
116
  const inheritedEnvironment = Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== void 0));
112
117
  inheritedEnvironment.TERM = "xterm-256color";
118
+ if (this.sshAgentDiscovery.platform === "darwin" && inheritedEnvironment.SSH_AUTH_SOCK === void 0 && input.env.SSH_AUTH_SOCK === void 0) await execute(this.sshAgentDiscovery.launchctlPath, ["getenv", "SSH_AUTH_SOCK"], {
119
+ timeout: 1e3,
120
+ killSignal: "SIGKILL",
121
+ maxBuffer: 4096
122
+ }).then(async ({ stdout }) => {
123
+ const socket = stdout.trim();
124
+ if (!socket) return;
125
+ if (!path.isAbsolute(socket) || /\p{Cc}/u.test(socket) || !(await fs.stat(socket)).isSocket()) throw new Error("Invalid launchd SSH agent socket");
126
+ inheritedEnvironment.SSH_AUTH_SOCK = socket;
127
+ }).catch(() => {
128
+ console.warn("[Treeport terminal host] Could not discover the macOS SSH agent. Continuing without SSH_AUTH_SOCK; check `launchctl getenv SSH_AUTH_SOCK` or set SSH_AUTH_SOCK explicitly for the terminal.");
129
+ });
113
130
  const directLaunch = integrateShellLaunch(input.argv, {
114
131
  ...inheritedEnvironment,
115
132
  ...input.env