webmux 0.7.3 → 0.8.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.
package/README.md CHANGED
@@ -34,6 +34,12 @@ Track dev server ports across worktrees. webmux polls configured services and sh
34
34
 
35
35
  Run agents in isolated Docker containers for untrusted or experimental work. webmux manages the container lifecycle, port forwarding, and volume mounts automatically.
36
36
 
37
+ ### Linear Integration
38
+
39
+ ![linear-1 5x](https://github.com/user-attachments/assets/3187fbe2-eeee-4a33-8780-c51c3575b72a)
40
+
41
+ See your assigned Linear issues alongside your worktrees. Webmux matches branches to issues automatically, so you can browse your backlog, pick an issue, and spin up a worktree for it in one click.
42
+
37
43
  ## Quick Start
38
44
 
39
45
  ```bash
@@ -6905,7 +6905,7 @@ var require_public_api = __commonJS((exports) => {
6905
6905
  });
6906
6906
 
6907
6907
  // backend/src/server.ts
6908
- import { join as join5, resolve as resolve5 } from "path";
6908
+ import { join as join6, resolve as resolve5 } from "path";
6909
6909
  import { networkInterfaces } from "os";
6910
6910
 
6911
6911
  // backend/src/lib/log.ts
@@ -7931,6 +7931,7 @@ async function removeContainer(branch) {
7931
7931
  }
7932
7932
 
7933
7933
  // backend/src/adapters/hooks.ts
7934
+ import { join as join2 } from "path";
7934
7935
  function buildErrorMessage(name, exitCode, stdout, stderr) {
7935
7936
  const output = stderr.trim() || stdout.trim();
7936
7937
  if (output) {
@@ -7938,10 +7939,30 @@ function buildErrorMessage(name, exitCode, stdout, stderr) {
7938
7939
  }
7939
7940
  return `${name} hook failed (exit ${exitCode})`;
7940
7941
  }
7942
+ function hasDirenv() {
7943
+ return Bun.spawnSync(["direnv", "version"], { stdout: "pipe", stderr: "pipe" }).exitCode === 0;
7944
+ }
7941
7945
 
7942
7946
  class BunLifecycleHookRunner {
7947
+ direnvAvailable = null;
7948
+ checkDirenv() {
7949
+ if (this.direnvAvailable === null) {
7950
+ this.direnvAvailable = hasDirenv();
7951
+ }
7952
+ return this.direnvAvailable;
7953
+ }
7954
+ async buildCommand(cwd, command) {
7955
+ if (this.checkDirenv() && await Bun.file(join2(cwd, ".envrc")).exists()) {
7956
+ Bun.spawnSync(["direnv", "allow"], { cwd, stdout: "pipe", stderr: "pipe" });
7957
+ return ["direnv", "exec", cwd, "bash", "-c", command];
7958
+ }
7959
+ return ["bash", "-c", command];
7960
+ }
7943
7961
  async run(input) {
7944
- const proc = Bun.spawn(["bash", "-lc", input.command], {
7962
+ const cmd = await this.buildCommand(input.cwd, input.command);
7963
+ console.debug(`[hook-runner] Spawning: ${cmd.join(" ")} cwd=${input.cwd}`);
7964
+ console.debug(`[hook-runner] Env keys: ${Object.keys(input.env).join(", ")}`);
7965
+ const proc = Bun.spawn(cmd, {
7945
7966
  cwd: input.cwd,
7946
7967
  env: {
7947
7968
  ...Bun.env,
@@ -7955,6 +7976,11 @@ class BunLifecycleHookRunner {
7955
7976
  new Response(proc.stdout).text(),
7956
7977
  new Response(proc.stderr).text()
7957
7978
  ]);
7979
+ console.debug(`[hook-runner] ${input.name} exitCode=${exitCode}`);
7980
+ if (stdout.trim())
7981
+ console.debug(`[hook-runner] stdout: ${stdout.trim()}`);
7982
+ if (stderr.trim())
7983
+ console.debug(`[hook-runner] stderr: ${stderr.trim()}`);
7958
7984
  if (exitCode !== 0) {
7959
7985
  throw new Error(buildErrorMessage(input.name, exitCode, stdout, stderr));
7960
7986
  }
@@ -8678,11 +8704,11 @@ import { dirname as dirname3, resolve as resolve3 } from "path";
8678
8704
 
8679
8705
  // backend/src/adapters/agent-runtime.ts
8680
8706
  import { chmod as chmod2, mkdir as mkdir3 } from "fs/promises";
8681
- import { dirname as dirname2, join as join3 } from "path";
8707
+ import { dirname as dirname2, join as join4 } from "path";
8682
8708
 
8683
8709
  // backend/src/adapters/fs.ts
8684
8710
  import { mkdir as mkdir2 } from "fs/promises";
8685
- import { join as join2 } from "path";
8711
+ import { join as join3 } from "path";
8686
8712
  var SAFE_ENV_VALUE_RE = /^[A-Za-z0-9_./:@%+=,-]+$/;
8687
8713
  function stringifyAllocatedPorts(ports) {
8688
8714
  const entries = Object.entries(ports).map(([key, value]) => [key, String(value)]);
@@ -8694,14 +8720,14 @@ function quoteEnvValue(value) {
8694
8720
  return `'${value.replaceAll("'", "'\\''")}'`;
8695
8721
  }
8696
8722
  function getWorktreeStoragePaths(gitDir) {
8697
- const webmuxDir = join2(gitDir, "webmux");
8723
+ const webmuxDir = join3(gitDir, "webmux");
8698
8724
  return {
8699
8725
  gitDir,
8700
8726
  webmuxDir,
8701
- metaPath: join2(webmuxDir, "meta.json"),
8702
- runtimeEnvPath: join2(webmuxDir, "runtime.env"),
8703
- controlEnvPath: join2(webmuxDir, "control.env"),
8704
- prsPath: join2(webmuxDir, "prs.json")
8727
+ metaPath: join3(webmuxDir, "meta.json"),
8728
+ runtimeEnvPath: join3(webmuxDir, "runtime.env"),
8729
+ controlEnvPath: join3(webmuxDir, "control.env"),
8730
+ prsPath: join3(webmuxDir, "prs.json")
8705
8731
  };
8706
8732
  }
8707
8733
  async function ensureWorktreeStorageDirs(gitDir) {
@@ -9053,8 +9079,8 @@ async function mergeClaudeSettings(settingsPath, hookSettings) {
9053
9079
  async function ensureAgentRuntimeArtifacts(input) {
9054
9080
  const storagePaths = getWorktreeStoragePaths(input.gitDir);
9055
9081
  const artifacts = {
9056
- agentCtlPath: join3(storagePaths.webmuxDir, "webmux-agentctl"),
9057
- claudeSettingsPath: join3(input.worktreePath, ".claude", "settings.local.json")
9082
+ agentCtlPath: join4(storagePaths.webmuxDir, "webmux-agentctl"),
9083
+ claudeSettingsPath: join4(input.worktreePath, ".claude", "settings.local.json")
9058
9084
  };
9059
9085
  await mkdir3(dirname2(artifacts.claudeSettingsPath), { recursive: true });
9060
9086
  await Bun.write(artifacts.agentCtlPath, buildAgentCtlScript());
@@ -9710,9 +9736,12 @@ class LifecycleService {
9710
9736
  await this.deps.reconciliation.reconcile(this.deps.projectRoot);
9711
9737
  }
9712
9738
  async runLifecycleHook(input) {
9739
+ console.debug(`[lifecycle-hook] name=${input.name} command=${input.command ?? "UNDEFINED"} meta=${input.meta ? "present" : "NULL"} cwd=${input.worktreePath}`);
9713
9740
  if (!input.command || !input.meta) {
9741
+ console.debug(`[lifecycle-hook] SKIPPING ${input.name}: command=${!!input.command} meta=${!!input.meta}`);
9714
9742
  return;
9715
9743
  }
9744
+ console.debug(`[lifecycle-hook] RUNNING ${input.name}: ${input.command} in ${input.worktreePath}`);
9716
9745
  await this.deps.hooks.run({
9717
9746
  name: input.name,
9718
9747
  command: input.command,
@@ -9721,6 +9750,7 @@ class LifecycleService {
9721
9750
  WEBMUX_WORKTREE_PATH: input.worktreePath
9722
9751
  })
9723
9752
  });
9753
+ console.debug(`[lifecycle-hook] COMPLETED ${input.name}`);
9724
9754
  }
9725
9755
  wrapOperationError(error) {
9726
9756
  if (error instanceof LifecycleError) {
@@ -10839,7 +10869,7 @@ Bun.serve({
10839
10869
  if (STATIC_DIR) {
10840
10870
  const url = new URL(req.url);
10841
10871
  const rawPath = url.pathname === "/" ? "index.html" : url.pathname;
10842
- const filePath = join5(STATIC_DIR, rawPath);
10872
+ const filePath = join6(STATIC_DIR, rawPath);
10843
10873
  const staticRoot = resolve5(STATIC_DIR);
10844
10874
  if (!resolve5(filePath).startsWith(staticRoot + "/")) {
10845
10875
  return new Response("Forbidden", { status: 403 });
@@ -10849,7 +10879,7 @@ Bun.serve({
10849
10879
  const headers = rawPath.startsWith("/assets/") ? { "Cache-Control": "public, max-age=31536000, immutable" } : {};
10850
10880
  return new Response(file, { headers });
10851
10881
  }
10852
- return new Response(Bun.file(join5(STATIC_DIR, "index.html")), {
10882
+ return new Response(Bun.file(join6(STATIC_DIR, "index.html")), {
10853
10883
  headers: { "Cache-Control": "no-cache" }
10854
10884
  });
10855
10885
  }
package/bin/webmux.js CHANGED
@@ -1557,7 +1557,7 @@ function createAgentStreamPrinter(label) {
1557
1557
  }
1558
1558
  };
1559
1559
  }
1560
- var deps, gitRoot, missing, webmuxYaml;
1560
+ var deps, gitRoot, missing, MIN_BUN_VERSION = "1.3.5", bunVersionResult, webmuxYaml;
1561
1561
  var init_init = __esm(async () => {
1562
1562
  init_dist2();
1563
1563
  init_shared();
@@ -1589,6 +1589,19 @@ var init_init = __esm(async () => {
1589
1589
  Gt("Setup incomplete.");
1590
1590
  process.exit(1);
1591
1591
  }
1592
+ bunVersionResult = run("bun", ["--version"]);
1593
+ if (bunVersionResult.success) {
1594
+ const bunVersion = bunVersionResult.stdout.toString().trim();
1595
+ const [major, minor, patch] = bunVersion.split(".").map(Number);
1596
+ const [reqMajor, reqMinor, reqPatch] = MIN_BUN_VERSION.split(".").map(Number);
1597
+ const tooOld = major < reqMajor || major === reqMajor && minor < reqMinor || major === reqMajor && minor === reqMinor && patch < reqPatch;
1598
+ if (tooOld) {
1599
+ R2.error(`Bun ${bunVersion} is too old. webmux requires Bun >= ${MIN_BUN_VERSION}.`);
1600
+ R2.info("Upgrade with: bun upgrade");
1601
+ Gt("Setup incomplete.");
1602
+ process.exit(1);
1603
+ }
1604
+ }
1592
1605
  if (which("gh")) {
1593
1606
  const ghAuth = run("gh", ["auth", "status"]);
1594
1607
  if (!ghAuth.success) {