webmux 0.39.0 → 0.41.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/backend/dist/server.js +91 -21
- package/bin/webmux.js +383 -148
- package/frontend/dist/assets/{DiffDialog-CqFJ8AzJ.js → DiffDialog-DSiqODW5.js} +1 -1
- package/frontend/dist/assets/{index-CghYughj.css → index-C4i4-u87.css} +1 -1
- package/frontend/dist/assets/index-C_8-_vLc.js +39 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-BnMSlB_K.js +0 -39
package/bin/webmux.js
CHANGED
|
@@ -473,7 +473,8 @@ _webmux() {
|
|
|
473
473
|
'remove:Remove a worktree'
|
|
474
474
|
'merge:Merge a worktree into main'
|
|
475
475
|
'send:Send a prompt to a running worktree agent'
|
|
476
|
-
'prune:Remove all worktrees in the current project'
|
|
476
|
+
'prune:Remove all closed (not open) worktrees in the current project'
|
|
477
|
+
'restore:Re-open all worktree sessions that were open before'
|
|
477
478
|
'linear:Post a worktree conversation to a Linear issue/team'
|
|
478
479
|
'project:List, add, or remove projects served by the dashboard'
|
|
479
480
|
'completion:Generate shell completion script'
|
|
@@ -543,7 +544,7 @@ compdef _webmux webmux`, BASH_SCRIPT = `_webmux() {
|
|
|
543
544
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
544
545
|
|
|
545
546
|
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
546
|
-
COMPREPLY=($(compgen -W "serve init service update add oneshot list open close refresh archive unarchive label remove merge send prune linear project completion" -- "\${cur}"))
|
|
547
|
+
COMPREPLY=($(compgen -W "serve init service update add oneshot list open close refresh archive unarchive label remove merge send prune restore linear project completion" -- "\${cur}"))
|
|
547
548
|
return
|
|
548
549
|
fi
|
|
549
550
|
|
|
@@ -5996,7 +5997,7 @@ var init_zod = __esm(() => {
|
|
|
5996
5997
|
init_external();
|
|
5997
5998
|
});
|
|
5998
5999
|
|
|
5999
|
-
// node_modules/.bun/@ts-rest+core@3.52.1+
|
|
6000
|
+
// node_modules/.bun/@ts-rest+core@3.52.1+38b2c5aebf202339/node_modules/@ts-rest/core/index.esm.mjs
|
|
6000
6001
|
var isZodType = (obj) => {
|
|
6001
6002
|
return typeof (obj === null || obj === undefined ? undefined : obj.safeParse) === "function";
|
|
6002
6003
|
}, isZodObjectStrict = (obj) => {
|
|
@@ -8338,21 +8339,110 @@ ${result.stderr.trim()}` : ""
|
|
|
8338
8339
|
console.log();
|
|
8339
8340
|
});
|
|
8340
8341
|
|
|
8342
|
+
// backend/src/domain/projects.ts
|
|
8343
|
+
function isProjectEntry(value) {
|
|
8344
|
+
if (typeof value !== "object" || value === null)
|
|
8345
|
+
return false;
|
|
8346
|
+
const v = value;
|
|
8347
|
+
return typeof v.path === "string" && v.path.length > 0 && typeof v.name === "string" && typeof v.addedAt === "number";
|
|
8348
|
+
}
|
|
8349
|
+
|
|
8350
|
+
// backend/src/lib/log.ts
|
|
8351
|
+
function ts() {
|
|
8352
|
+
return new Date().toISOString().slice(11, 23);
|
|
8353
|
+
}
|
|
8354
|
+
var DEBUG, log2;
|
|
8355
|
+
var init_log = __esm(() => {
|
|
8356
|
+
DEBUG = Bun.env.WEBMUX_DEBUG === "1";
|
|
8357
|
+
log2 = {
|
|
8358
|
+
info(msg) {
|
|
8359
|
+
console.log(`[${ts()}] ${msg}`);
|
|
8360
|
+
},
|
|
8361
|
+
debug(msg) {
|
|
8362
|
+
if (DEBUG)
|
|
8363
|
+
console.log(`[${ts()}] ${msg}`);
|
|
8364
|
+
},
|
|
8365
|
+
warn(msg) {
|
|
8366
|
+
console.warn(`[${ts()}] ${msg}`);
|
|
8367
|
+
},
|
|
8368
|
+
error(msg, err) {
|
|
8369
|
+
err !== undefined ? console.error(`[${ts()}] ${msg}`, err) : console.error(`[${ts()}] ${msg}`);
|
|
8370
|
+
}
|
|
8371
|
+
};
|
|
8372
|
+
});
|
|
8373
|
+
|
|
8374
|
+
// backend/src/adapters/projects-registry.ts
|
|
8375
|
+
import { mkdirSync, readFileSync as readFileSync3, renameSync, writeFileSync } from "fs";
|
|
8376
|
+
import { homedir } from "os";
|
|
8377
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
8378
|
+
function defaultRegistryFile() {
|
|
8379
|
+
return join5(homedir(), ".webmux", "projects.json");
|
|
8380
|
+
}
|
|
8381
|
+
function createProjectsRegistry(file = defaultRegistryFile()) {
|
|
8382
|
+
function read() {
|
|
8383
|
+
let raw;
|
|
8384
|
+
try {
|
|
8385
|
+
raw = readFileSync3(file, "utf8");
|
|
8386
|
+
} catch {
|
|
8387
|
+
return [];
|
|
8388
|
+
}
|
|
8389
|
+
let parsed;
|
|
8390
|
+
try {
|
|
8391
|
+
parsed = JSON.parse(raw);
|
|
8392
|
+
} catch {
|
|
8393
|
+
log2.debug(`[projects-registry] ignoring malformed ${file}`);
|
|
8394
|
+
return [];
|
|
8395
|
+
}
|
|
8396
|
+
if (!Array.isArray(parsed))
|
|
8397
|
+
return [];
|
|
8398
|
+
return parsed.filter(isProjectEntry);
|
|
8399
|
+
}
|
|
8400
|
+
function write(entries) {
|
|
8401
|
+
mkdirSync(dirname3(file), { recursive: true });
|
|
8402
|
+
const tmpPath = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
8403
|
+
writeFileSync(tmpPath, `${JSON.stringify(entries, null, 2)}
|
|
8404
|
+
`);
|
|
8405
|
+
renameSync(tmpPath, file);
|
|
8406
|
+
}
|
|
8407
|
+
return {
|
|
8408
|
+
list() {
|
|
8409
|
+
return read();
|
|
8410
|
+
},
|
|
8411
|
+
add(entry) {
|
|
8412
|
+
const entries = read().filter((existing) => existing.path !== entry.path);
|
|
8413
|
+
entries.push(entry);
|
|
8414
|
+
write(entries);
|
|
8415
|
+
},
|
|
8416
|
+
remove(path) {
|
|
8417
|
+
const entries = read();
|
|
8418
|
+
const next = entries.filter((entry) => entry.path !== path);
|
|
8419
|
+
if (next.length !== entries.length)
|
|
8420
|
+
write(next);
|
|
8421
|
+
}
|
|
8422
|
+
};
|
|
8423
|
+
}
|
|
8424
|
+
var init_projects_registry = __esm(() => {
|
|
8425
|
+
init_log();
|
|
8426
|
+
});
|
|
8427
|
+
|
|
8341
8428
|
// bin/src/service.ts
|
|
8342
8429
|
var exports_service = {};
|
|
8343
8430
|
__export(exports_service, {
|
|
8431
|
+
shouldPersistProject: () => shouldPersistProject,
|
|
8344
8432
|
resolveEnvVars: () => resolveEnvVars,
|
|
8433
|
+
resolveConfirmDecision: () => resolveConfirmDecision,
|
|
8345
8434
|
readPortFromUnit: () => readPortFromUnit,
|
|
8346
8435
|
readEnvVarsFromUnit: () => readEnvVarsFromUnit,
|
|
8347
8436
|
parseInstalledServiceConfig: () => parseInstalledServiceConfig,
|
|
8348
8437
|
parseEnvCliArgs: () => parseEnvCliArgs,
|
|
8438
|
+
migrateServedRepoFromUnit: () => migrateServedRepoFromUnit,
|
|
8349
8439
|
generateServiceFile: () => generateServiceFile,
|
|
8350
8440
|
default: () => service,
|
|
8351
8441
|
AUTO_PICKUP_ENV_VARS: () => AUTO_PICKUP_ENV_VARS
|
|
8352
8442
|
});
|
|
8353
|
-
import { chmodSync, existsSync as existsSync4, mkdirSync, readFileSync as
|
|
8354
|
-
import { basename as basename3, join as
|
|
8355
|
-
import { homedir } from "os";
|
|
8443
|
+
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync } from "fs";
|
|
8444
|
+
import { basename as basename3, join as join6 } from "path";
|
|
8445
|
+
import { homedir as homedir2 } from "os";
|
|
8356
8446
|
function getPlatform() {
|
|
8357
8447
|
const plat = process.platform;
|
|
8358
8448
|
if (plat === "linux" || plat === "darwin")
|
|
@@ -8365,6 +8455,33 @@ function resolveWebmuxPath() {
|
|
|
8365
8455
|
return null;
|
|
8366
8456
|
return result.stdout.toString().trim();
|
|
8367
8457
|
}
|
|
8458
|
+
function shouldPersistProject(root, hasWebmuxConfig, existingPaths) {
|
|
8459
|
+
if (!root || !hasWebmuxConfig)
|
|
8460
|
+
return false;
|
|
8461
|
+
return !existingPaths.includes(root);
|
|
8462
|
+
}
|
|
8463
|
+
function hasWebmuxConfig(root) {
|
|
8464
|
+
return existsSync4(join6(root, ".webmux.yaml")) || existsSync4(join6(root, ".webmux.local.yaml"));
|
|
8465
|
+
}
|
|
8466
|
+
function persistProject(root, registry) {
|
|
8467
|
+
if (!root)
|
|
8468
|
+
return null;
|
|
8469
|
+
const existingPaths = registry.list().map((entry) => entry.path);
|
|
8470
|
+
if (!shouldPersistProject(root, hasWebmuxConfig(root), existingPaths))
|
|
8471
|
+
return null;
|
|
8472
|
+
registry.add({ path: root, name: detectProjectName(root), addedAt: Date.now() });
|
|
8473
|
+
return root;
|
|
8474
|
+
}
|
|
8475
|
+
function cwdProjectToPersist() {
|
|
8476
|
+
const gitRoot2 = getGitRoot();
|
|
8477
|
+
if (!gitRoot2)
|
|
8478
|
+
return null;
|
|
8479
|
+
const existingPaths = createProjectsRegistry().list().map((entry) => entry.path);
|
|
8480
|
+
return shouldPersistProject(gitRoot2, hasWebmuxConfig(gitRoot2), existingPaths) ? gitRoot2 : null;
|
|
8481
|
+
}
|
|
8482
|
+
function migrateServedRepoFromUnit(filePath, platform, registry = createProjectsRegistry()) {
|
|
8483
|
+
return persistProject(readWorkingDirFromUnit(filePath, platform), registry);
|
|
8484
|
+
}
|
|
8368
8485
|
function formatCommand([bin, args]) {
|
|
8369
8486
|
return [bin, ...args].join(" ");
|
|
8370
8487
|
}
|
|
@@ -8378,10 +8495,10 @@ function printRunResult(result) {
|
|
|
8378
8495
|
console.error(err);
|
|
8379
8496
|
}
|
|
8380
8497
|
function systemdUnitPath(serviceName) {
|
|
8381
|
-
return
|
|
8498
|
+
return join6(homedir2(), ".config", "systemd", "user", `${serviceName}.service`);
|
|
8382
8499
|
}
|
|
8383
8500
|
function launchdPlistPath(serviceName) {
|
|
8384
|
-
return
|
|
8501
|
+
return join6(homedir2(), "Library", "LaunchAgents", `com.webmux.${serviceName}.plist`);
|
|
8385
8502
|
}
|
|
8386
8503
|
function serviceFilePath(config) {
|
|
8387
8504
|
if (config.platform === "linux")
|
|
@@ -8392,16 +8509,15 @@ function generateSystemdUnit(config) {
|
|
|
8392
8509
|
const extra = Object.keys(config.envVars).sort().map((key) => `Environment=${key}=${config.envVars[key]}`).join(`
|
|
8393
8510
|
`);
|
|
8394
8511
|
return `[Unit]
|
|
8395
|
-
Description=webmux dashboard
|
|
8512
|
+
Description=webmux dashboard
|
|
8396
8513
|
|
|
8397
8514
|
[Service]
|
|
8398
8515
|
Type=simple
|
|
8399
8516
|
ExecStart=${config.webmuxPath} serve --port ${config.port}
|
|
8400
|
-
WorkingDirectory=${
|
|
8517
|
+
WorkingDirectory=${homedir2()}
|
|
8401
8518
|
Restart=on-failure
|
|
8402
8519
|
RestartSec=5
|
|
8403
8520
|
Environment=PORT=${config.port}
|
|
8404
|
-
Environment=WEBMUX_PROJECT_DIR=${config.projectDir}
|
|
8405
8521
|
Environment=PATH=${process.env.PATH}${extra ? `
|
|
8406
8522
|
` + extra : ""}
|
|
8407
8523
|
|
|
@@ -8413,7 +8529,7 @@ function escapePlistText(value) {
|
|
|
8413
8529
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
8414
8530
|
}
|
|
8415
8531
|
function generateLaunchdPlist(config) {
|
|
8416
|
-
const logPath =
|
|
8532
|
+
const logPath = join6(homedir2(), "Library", "Logs", `webmux-${config.serviceName}.log`);
|
|
8417
8533
|
const extra = Object.keys(config.envVars).sort().map((key) => ` <key>${escapePlistText(key)}</key>
|
|
8418
8534
|
<string>${escapePlistText(config.envVars[key])}</string>`).join(`
|
|
8419
8535
|
`);
|
|
@@ -8431,7 +8547,7 @@ function generateLaunchdPlist(config) {
|
|
|
8431
8547
|
<string>${config.port}</string>
|
|
8432
8548
|
</array>
|
|
8433
8549
|
<key>WorkingDirectory</key>
|
|
8434
|
-
<string>${
|
|
8550
|
+
<string>${homedir2()}</string>
|
|
8435
8551
|
<key>RunAtLoad</key>
|
|
8436
8552
|
<true/>
|
|
8437
8553
|
<key>KeepAlive</key>
|
|
@@ -8447,8 +8563,6 @@ function generateLaunchdPlist(config) {
|
|
|
8447
8563
|
<dict>
|
|
8448
8564
|
<key>PORT</key>
|
|
8449
8565
|
<string>${config.port}</string>
|
|
8450
|
-
<key>WEBMUX_PROJECT_DIR</key>
|
|
8451
|
-
<string>${config.projectDir}</string>
|
|
8452
8566
|
<key>PATH</key>
|
|
8453
8567
|
<string>${process.env.PATH}</string>${extra ? `
|
|
8454
8568
|
` + extra : ""}
|
|
@@ -8465,7 +8579,7 @@ function generateServiceFile(config) {
|
|
|
8465
8579
|
function readWorkingDirFromUnit(filePath, platform) {
|
|
8466
8580
|
let text;
|
|
8467
8581
|
try {
|
|
8468
|
-
text =
|
|
8582
|
+
text = readFileSync4(filePath, "utf8");
|
|
8469
8583
|
} catch {
|
|
8470
8584
|
return null;
|
|
8471
8585
|
}
|
|
@@ -8476,7 +8590,7 @@ function readWorkingDirFromUnit(filePath, platform) {
|
|
|
8476
8590
|
function readPortFromUnit(filePath) {
|
|
8477
8591
|
let text;
|
|
8478
8592
|
try {
|
|
8479
|
-
text =
|
|
8593
|
+
text = readFileSync4(filePath, "utf8");
|
|
8480
8594
|
} catch {
|
|
8481
8595
|
return null;
|
|
8482
8596
|
}
|
|
@@ -8490,7 +8604,7 @@ function unescapePlistText(value) {
|
|
|
8490
8604
|
function readEnvVarsFromUnit(filePath, platform) {
|
|
8491
8605
|
let text;
|
|
8492
8606
|
try {
|
|
8493
|
-
text =
|
|
8607
|
+
text = readFileSync4(filePath, "utf8");
|
|
8494
8608
|
} catch {
|
|
8495
8609
|
return {};
|
|
8496
8610
|
}
|
|
@@ -8519,19 +8633,13 @@ function parseInstalledServiceConfig(filePath, platform, webmuxPath) {
|
|
|
8519
8633
|
const port = readPortFromUnit(filePath);
|
|
8520
8634
|
if (port === null)
|
|
8521
8635
|
return null;
|
|
8522
|
-
const projectDir = readWorkingDirFromUnit(filePath, platform);
|
|
8523
|
-
if (projectDir === null)
|
|
8524
|
-
return null;
|
|
8525
8636
|
const fileBase = basename3(filePath);
|
|
8526
8637
|
const serviceName = platform === "linux" ? fileBase.replace(/\.service$/, "") : fileBase.replace(/^com\.webmux\./, "").replace(/\.plist$/, "");
|
|
8527
|
-
const projectName = detectProjectName(projectDir);
|
|
8528
8638
|
const envVars = readEnvVarsFromUnit(filePath, platform);
|
|
8529
8639
|
return {
|
|
8530
8640
|
platform,
|
|
8531
|
-
projectName,
|
|
8532
8641
|
serviceName,
|
|
8533
8642
|
webmuxPath,
|
|
8534
|
-
projectDir,
|
|
8535
8643
|
port,
|
|
8536
8644
|
envVars
|
|
8537
8645
|
};
|
|
@@ -8626,19 +8734,19 @@ function redactSecretsInUnit(content, envVars) {
|
|
|
8626
8734
|
}
|
|
8627
8735
|
return out;
|
|
8628
8736
|
}
|
|
8629
|
-
|
|
8737
|
+
function isInteractive() {
|
|
8738
|
+
return Boolean(process.stdin.isTTY);
|
|
8739
|
+
}
|
|
8740
|
+
function resolveConfirmDecision(autoConfirm, interactive) {
|
|
8741
|
+
if (autoConfirm)
|
|
8742
|
+
return "proceed";
|
|
8743
|
+
if (!interactive)
|
|
8744
|
+
return "abort-noninteractive";
|
|
8745
|
+
return "prompt";
|
|
8746
|
+
}
|
|
8747
|
+
async function install(config, portExplicit, envVarNotes, autoConfirm) {
|
|
8630
8748
|
const filePath = serviceFilePath(config);
|
|
8631
8749
|
const alreadyInstalled = isInstalled(config);
|
|
8632
|
-
if (alreadyInstalled) {
|
|
8633
|
-
const reinstall = await confirm({ message: "Service is already installed. Reinstall?" });
|
|
8634
|
-
if (isCancel(reinstall) || !reinstall) {
|
|
8635
|
-
log.info("Aborted.");
|
|
8636
|
-
return;
|
|
8637
|
-
}
|
|
8638
|
-
for (const cmd of uninstallCommands(config)) {
|
|
8639
|
-
runCommand(cmd);
|
|
8640
|
-
}
|
|
8641
|
-
}
|
|
8642
8750
|
const requestedPort = config.port;
|
|
8643
8751
|
let chosenPort = requestedPort;
|
|
8644
8752
|
let portNote = null;
|
|
@@ -8652,14 +8760,17 @@ async function install(config, portExplicit, envVarNotes) {
|
|
|
8652
8760
|
config = { ...config, port: chosenPort };
|
|
8653
8761
|
const content = generateServiceFile(config);
|
|
8654
8762
|
const commands = installCommands(config);
|
|
8763
|
+
const persistPath = cwdProjectToPersist();
|
|
8655
8764
|
const displayContent = redactSecretsInUnit(content, config.envVars);
|
|
8656
8765
|
note([
|
|
8766
|
+
...alreadyInstalled ? ["Service is already installed \u2014 this will reinstall it.", ""] : [],
|
|
8657
8767
|
`File: ${filePath}`,
|
|
8658
8768
|
"",
|
|
8659
8769
|
"Contents:",
|
|
8660
8770
|
displayContent,
|
|
8661
8771
|
"Commands to run:",
|
|
8662
|
-
...commands.map((c4) => ` $ ${formatCommand(c4)}`)
|
|
8772
|
+
...commands.map((c4) => ` $ ${formatCommand(c4)}`),
|
|
8773
|
+
...persistPath ? ["", `Will also register this project: ${persistPath}`] : []
|
|
8663
8774
|
].join(`
|
|
8664
8775
|
`), "Install service");
|
|
8665
8776
|
if (Object.keys(config.envVars).length > 0) {
|
|
@@ -8669,12 +8780,28 @@ ${envVarNotes.join(`
|
|
|
8669
8780
|
}
|
|
8670
8781
|
if (portNote)
|
|
8671
8782
|
log.info(portNote);
|
|
8672
|
-
const
|
|
8673
|
-
if (
|
|
8674
|
-
log.info("
|
|
8783
|
+
const decision = resolveConfirmDecision(autoConfirm, isInteractive());
|
|
8784
|
+
if (decision === "abort-noninteractive") {
|
|
8785
|
+
log.info(`Non-interactive environment \u2014 not ${alreadyInstalled ? "reinstalling" : "installing"}. ` + "Re-run with --yes to confirm and apply the plan above.");
|
|
8675
8786
|
return;
|
|
8676
8787
|
}
|
|
8677
|
-
|
|
8788
|
+
if (decision === "prompt") {
|
|
8789
|
+
const ok = await confirm({ message: alreadyInstalled ? "Reinstall?" : "Proceed?" });
|
|
8790
|
+
if (isCancel(ok) || !ok) {
|
|
8791
|
+
log.info("Aborted.");
|
|
8792
|
+
return;
|
|
8793
|
+
}
|
|
8794
|
+
}
|
|
8795
|
+
if (alreadyInstalled) {
|
|
8796
|
+
const migrated = migrateServedRepoFromUnit(filePath, config.platform);
|
|
8797
|
+
if (migrated) {
|
|
8798
|
+
log.success(`Migrated previously-served project ${detectProjectName(migrated)} (${migrated})`);
|
|
8799
|
+
}
|
|
8800
|
+
for (const cmd of uninstallCommands(config)) {
|
|
8801
|
+
runCommand(cmd);
|
|
8802
|
+
}
|
|
8803
|
+
}
|
|
8804
|
+
mkdirSync2(filePath.substring(0, filePath.lastIndexOf("/")), { recursive: true });
|
|
8678
8805
|
await Bun.write(filePath, content);
|
|
8679
8806
|
if (Object.keys(config.envVars).length > 0) {
|
|
8680
8807
|
try {
|
|
@@ -8684,6 +8811,14 @@ ${envVarNotes.join(`
|
|
|
8684
8811
|
}
|
|
8685
8812
|
}
|
|
8686
8813
|
log.success(`Wrote ${filePath}`);
|
|
8814
|
+
if (persistPath) {
|
|
8815
|
+
createProjectsRegistry().add({
|
|
8816
|
+
path: persistPath,
|
|
8817
|
+
name: detectProjectName(persistPath),
|
|
8818
|
+
addedAt: Date.now()
|
|
8819
|
+
});
|
|
8820
|
+
log.success(`Registered project ${detectProjectName(persistPath)} (${persistPath})`);
|
|
8821
|
+
}
|
|
8687
8822
|
for (const cmd of commands) {
|
|
8688
8823
|
const result = runCommand(cmd);
|
|
8689
8824
|
if (!result.success) {
|
|
@@ -8755,7 +8890,7 @@ function logs(config) {
|
|
|
8755
8890
|
if (config.platform === "linux") {
|
|
8756
8891
|
proc = Bun.spawn(["journalctl", "--user", "-u", config.serviceName, "-f", "--no-pager"], { stdout: "inherit", stderr: "inherit" });
|
|
8757
8892
|
} else {
|
|
8758
|
-
const logPath =
|
|
8893
|
+
const logPath = join6(homedir2(), "Library", "Logs", `webmux-${config.serviceName}.log`);
|
|
8759
8894
|
if (!existsSync4(logPath)) {
|
|
8760
8895
|
log.error(`Log file not found: ${logPath}`);
|
|
8761
8896
|
return;
|
|
@@ -8784,6 +8919,9 @@ Usage:
|
|
|
8784
8919
|
Options:
|
|
8785
8920
|
--port N Pin the service to a port (default: 5111). On
|
|
8786
8921
|
reinstall without --port the existing port is kept.
|
|
8922
|
+
--yes, -y Skip the confirmation prompt and install. In a
|
|
8923
|
+
non-interactive shell (CI, pipe) install prints the
|
|
8924
|
+
plan and stops unless --yes is passed.
|
|
8787
8925
|
--env KEY=VALUE Bake an environment variable into the service
|
|
8788
8926
|
unit (repeatable). Reserved keys PORT,
|
|
8789
8927
|
WEBMUX_PROJECT_DIR, and PATH are rejected.
|
|
@@ -8812,11 +8950,6 @@ async function service(args) {
|
|
|
8812
8950
|
log.error(`Unsupported platform: ${process.platform}. Only linux and macOS are supported.`);
|
|
8813
8951
|
return;
|
|
8814
8952
|
}
|
|
8815
|
-
const gitRoot2 = getGitRoot();
|
|
8816
|
-
if (!gitRoot2) {
|
|
8817
|
-
log.error("Not inside a git repository.");
|
|
8818
|
-
return;
|
|
8819
|
-
}
|
|
8820
8953
|
const serviceManager = platform === "linux" ? "systemctl" : "launchctl";
|
|
8821
8954
|
const smResult = run("which", [serviceManager]);
|
|
8822
8955
|
if (!smResult.success) {
|
|
@@ -8831,6 +8964,7 @@ async function service(args) {
|
|
|
8831
8964
|
let port = parseInt(process.env.PORT || "5111");
|
|
8832
8965
|
let portExplicit = false;
|
|
8833
8966
|
let autoPickup = true;
|
|
8967
|
+
let autoConfirm = false;
|
|
8834
8968
|
for (let i2 = 1;i2 < args.length; i2++) {
|
|
8835
8969
|
if (args[i2] === "--port" && args[i2 + 1]) {
|
|
8836
8970
|
const parsed = parseInt(args[++i2]);
|
|
@@ -8842,6 +8976,8 @@ async function service(args) {
|
|
|
8842
8976
|
portExplicit = true;
|
|
8843
8977
|
} else if (args[i2] === "--no-auto-env") {
|
|
8844
8978
|
autoPickup = false;
|
|
8979
|
+
} else if (args[i2] === "--yes" || args[i2] === "-y") {
|
|
8980
|
+
autoConfirm = true;
|
|
8845
8981
|
}
|
|
8846
8982
|
}
|
|
8847
8983
|
const cliEnv = parseEnvCliArgs(args.slice(1));
|
|
@@ -8850,7 +8986,6 @@ async function service(args) {
|
|
|
8850
8986
|
log.error(err);
|
|
8851
8987
|
return;
|
|
8852
8988
|
}
|
|
8853
|
-
const projectName = detectProjectName(gitRoot2);
|
|
8854
8989
|
const serviceName = "webmux";
|
|
8855
8990
|
let envVars = {};
|
|
8856
8991
|
let envVarNotes = [];
|
|
@@ -8867,16 +9002,14 @@ async function service(args) {
|
|
|
8867
9002
|
}
|
|
8868
9003
|
const config = {
|
|
8869
9004
|
platform,
|
|
8870
|
-
projectName,
|
|
8871
9005
|
serviceName,
|
|
8872
9006
|
webmuxPath,
|
|
8873
|
-
projectDir: gitRoot2,
|
|
8874
9007
|
port,
|
|
8875
9008
|
envVars
|
|
8876
9009
|
};
|
|
8877
9010
|
switch (action) {
|
|
8878
9011
|
case "install":
|
|
8879
|
-
await install(config, portExplicit, envVarNotes);
|
|
9012
|
+
await install(config, portExplicit, envVarNotes, autoConfirm);
|
|
8880
9013
|
break;
|
|
8881
9014
|
case "uninstall":
|
|
8882
9015
|
await uninstall(config);
|
|
@@ -8897,6 +9030,7 @@ var AUTO_PICKUP_ENV_VARS, RESERVED_ENV_KEYS, SYSTEMD_WORKDIR_RE, LAUNCHD_WORKDIR
|
|
|
8897
9030
|
var init_service = __esm(() => {
|
|
8898
9031
|
init_dist4();
|
|
8899
9032
|
init_shared();
|
|
9033
|
+
init_projects_registry();
|
|
8900
9034
|
AUTO_PICKUP_ENV_VARS = ["LINEAR_API_KEY"];
|
|
8901
9035
|
RESERVED_ENV_KEYS = new Set(["PORT", "WEBMUX_PROJECT_DIR", "PATH"]);
|
|
8902
9036
|
SYSTEMD_WORKDIR_RE = /^WorkingDirectory=(.+)$/m;
|
|
@@ -8916,9 +9050,9 @@ __export(exports_service_restart, {
|
|
|
8916
9050
|
restartCommand: () => restartCommand,
|
|
8917
9051
|
listInstalledServices: () => listInstalledServices
|
|
8918
9052
|
});
|
|
8919
|
-
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as
|
|
8920
|
-
import { homedir as
|
|
8921
|
-
import { join as
|
|
9053
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
|
|
9054
|
+
import { homedir as homedir3 } from "os";
|
|
9055
|
+
import { join as join7 } from "path";
|
|
8922
9056
|
function listInstalledServices(opts = {}) {
|
|
8923
9057
|
const out = [];
|
|
8924
9058
|
const systemdDir = opts.systemdDir ?? DEFAULT_SYSTEMD_DIR;
|
|
@@ -8930,7 +9064,7 @@ function listInstalledServices(opts = {}) {
|
|
|
8930
9064
|
continue;
|
|
8931
9065
|
out.push({
|
|
8932
9066
|
name: name.slice(0, -".service".length),
|
|
8933
|
-
filePath:
|
|
9067
|
+
filePath: join7(systemdDir, name),
|
|
8934
9068
|
platform: "linux"
|
|
8935
9069
|
});
|
|
8936
9070
|
}
|
|
@@ -8943,7 +9077,7 @@ function listInstalledServices(opts = {}) {
|
|
|
8943
9077
|
continue;
|
|
8944
9078
|
out.push({
|
|
8945
9079
|
name: name.slice(0, -".plist".length),
|
|
8946
|
-
filePath:
|
|
9080
|
+
filePath: join7(launchdDir, name),
|
|
8947
9081
|
platform: "darwin"
|
|
8948
9082
|
});
|
|
8949
9083
|
}
|
|
@@ -8986,17 +9120,19 @@ function reloadAfterRegenerate(service2, runner) {
|
|
|
8986
9120
|
service is now unloaded \u2014 recover with: launchctl load -w "${service2.filePath}"`
|
|
8987
9121
|
};
|
|
8988
9122
|
}
|
|
8989
|
-
async function updateInstalledService(service2, webmuxPath, runner = defaultRunner) {
|
|
9123
|
+
async function updateInstalledService(service2, webmuxPath, runner = defaultRunner, migrate = migrateServedRepoFromUnit) {
|
|
8990
9124
|
const canRegenerate = webmuxPath.length > 0;
|
|
8991
9125
|
const config = canRegenerate ? parseInstalledServiceConfig(service2.filePath, service2.platform, webmuxPath) : null;
|
|
8992
9126
|
let regenerated = false;
|
|
9127
|
+
let migratedProject;
|
|
8993
9128
|
if (config !== null) {
|
|
8994
9129
|
let currentContent = "";
|
|
8995
9130
|
try {
|
|
8996
|
-
currentContent =
|
|
9131
|
+
currentContent = readFileSync5(service2.filePath, "utf8");
|
|
8997
9132
|
} catch {}
|
|
8998
9133
|
const expected = generateServiceFile(config);
|
|
8999
9134
|
if (currentContent !== expected) {
|
|
9135
|
+
migratedProject = migrate(service2.filePath, service2.platform) ?? undefined;
|
|
9000
9136
|
try {
|
|
9001
9137
|
await Bun.write(service2.filePath, expected);
|
|
9002
9138
|
regenerated = true;
|
|
@@ -9005,6 +9141,7 @@ async function updateInstalledService(service2, webmuxPath, runner = defaultRunn
|
|
|
9005
9141
|
service: service2,
|
|
9006
9142
|
regenerated: false,
|
|
9007
9143
|
restarted: false,
|
|
9144
|
+
migratedProject,
|
|
9008
9145
|
error: `could not rewrite ${service2.filePath}: ${String(err)}`
|
|
9009
9146
|
};
|
|
9010
9147
|
}
|
|
@@ -9013,16 +9150,17 @@ async function updateInstalledService(service2, webmuxPath, runner = defaultRunn
|
|
|
9013
9150
|
if (regenerated) {
|
|
9014
9151
|
const reload = reloadAfterRegenerate(service2, runner);
|
|
9015
9152
|
if (!reload.ok) {
|
|
9016
|
-
return { service: service2, regenerated, restarted: false, error: reload.error };
|
|
9153
|
+
return { service: service2, regenerated, restarted: false, migratedProject, error: reload.error };
|
|
9017
9154
|
}
|
|
9018
9155
|
if (service2.platform === "darwin") {
|
|
9019
|
-
return { service: service2, regenerated, restarted: true };
|
|
9156
|
+
return { service: service2, regenerated, restarted: true, migratedProject };
|
|
9020
9157
|
}
|
|
9021
9158
|
}
|
|
9022
9159
|
const outcome = restartInstalledService(service2, runner);
|
|
9023
9160
|
return {
|
|
9024
9161
|
service: service2,
|
|
9025
9162
|
regenerated,
|
|
9163
|
+
migratedProject,
|
|
9026
9164
|
restarted: outcome.ok,
|
|
9027
9165
|
error: outcome.error
|
|
9028
9166
|
};
|
|
@@ -9032,40 +9170,16 @@ var init_service_restart = __esm(() => {
|
|
|
9032
9170
|
init_shared();
|
|
9033
9171
|
init_service();
|
|
9034
9172
|
defaultRunner = { run };
|
|
9035
|
-
DEFAULT_SYSTEMD_DIR =
|
|
9036
|
-
DEFAULT_LAUNCHD_DIR =
|
|
9037
|
-
});
|
|
9038
|
-
|
|
9039
|
-
// backend/src/lib/log.ts
|
|
9040
|
-
function ts() {
|
|
9041
|
-
return new Date().toISOString().slice(11, 23);
|
|
9042
|
-
}
|
|
9043
|
-
var DEBUG, log2;
|
|
9044
|
-
var init_log = __esm(() => {
|
|
9045
|
-
DEBUG = Bun.env.WEBMUX_DEBUG === "1";
|
|
9046
|
-
log2 = {
|
|
9047
|
-
info(msg) {
|
|
9048
|
-
console.log(`[${ts()}] ${msg}`);
|
|
9049
|
-
},
|
|
9050
|
-
debug(msg) {
|
|
9051
|
-
if (DEBUG)
|
|
9052
|
-
console.log(`[${ts()}] ${msg}`);
|
|
9053
|
-
},
|
|
9054
|
-
warn(msg) {
|
|
9055
|
-
console.warn(`[${ts()}] ${msg}`);
|
|
9056
|
-
},
|
|
9057
|
-
error(msg, err) {
|
|
9058
|
-
err !== undefined ? console.error(`[${ts()}] ${msg}`, err) : console.error(`[${ts()}] ${msg}`);
|
|
9059
|
-
}
|
|
9060
|
-
};
|
|
9173
|
+
DEFAULT_SYSTEMD_DIR = join7(homedir3(), ".config", "systemd", "user");
|
|
9174
|
+
DEFAULT_LAUNCHD_DIR = join7(homedir3(), "Library", "LaunchAgents");
|
|
9061
9175
|
});
|
|
9062
9176
|
|
|
9063
9177
|
// backend/src/adapters/instance-registry.ts
|
|
9064
|
-
import { mkdirSync as
|
|
9065
|
-
import { homedir as
|
|
9066
|
-
import { join as
|
|
9178
|
+
import { mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync6, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
9179
|
+
import { homedir as homedir4 } from "os";
|
|
9180
|
+
import { join as join8 } from "path";
|
|
9067
9181
|
function defaultRegistryDir() {
|
|
9068
|
-
return
|
|
9182
|
+
return join8(homedir4(), ".webmux", "instances");
|
|
9069
9183
|
}
|
|
9070
9184
|
function isAlive(pid) {
|
|
9071
9185
|
try {
|
|
@@ -9083,14 +9197,14 @@ function isInstanceEntry(value) {
|
|
|
9083
9197
|
}
|
|
9084
9198
|
function createInstanceRegistry(dir = defaultRegistryDir()) {
|
|
9085
9199
|
function ensureDir() {
|
|
9086
|
-
|
|
9200
|
+
mkdirSync3(dir, { recursive: true });
|
|
9087
9201
|
}
|
|
9088
9202
|
function entryPath(port) {
|
|
9089
|
-
return
|
|
9203
|
+
return join8(dir, `${port}.json`);
|
|
9090
9204
|
}
|
|
9091
9205
|
function readEntry(filename) {
|
|
9092
9206
|
try {
|
|
9093
|
-
const raw =
|
|
9207
|
+
const raw = readFileSync6(join8(dir, filename), "utf8");
|
|
9094
9208
|
const parsed = JSON.parse(raw);
|
|
9095
9209
|
return isInstanceEntry(parsed) ? parsed : null;
|
|
9096
9210
|
} catch {
|
|
@@ -9104,8 +9218,8 @@ function createInstanceRegistry(dir = defaultRegistryDir()) {
|
|
|
9104
9218
|
const tmpPath = `${finalPath}.${process.pid}.${Date.now()}.tmp`;
|
|
9105
9219
|
const text = `${JSON.stringify(entry, null, 2)}
|
|
9106
9220
|
`;
|
|
9107
|
-
|
|
9108
|
-
|
|
9221
|
+
writeFileSync2(tmpPath, text);
|
|
9222
|
+
renameSync2(tmpPath, finalPath);
|
|
9109
9223
|
},
|
|
9110
9224
|
deregister(port, expectedPid) {
|
|
9111
9225
|
if (expectedPid !== undefined) {
|
|
@@ -9138,7 +9252,7 @@ function createInstanceRegistry(dir = defaultRegistryDir()) {
|
|
|
9138
9252
|
continue;
|
|
9139
9253
|
if (!isAlive(entry.pid)) {
|
|
9140
9254
|
try {
|
|
9141
|
-
unlinkSync2(
|
|
9255
|
+
unlinkSync2(join8(dir, filename));
|
|
9142
9256
|
} catch {}
|
|
9143
9257
|
continue;
|
|
9144
9258
|
}
|
|
@@ -11317,11 +11431,11 @@ function conversationSessionId(conversation) {
|
|
|
11317
11431
|
return null;
|
|
11318
11432
|
return conversation.provider === "codexAppServer" ? conversation.threadId : conversation.sessionId;
|
|
11319
11433
|
}
|
|
11320
|
-
var WORKTREE_META_SCHEMA_VERSION = 1, WORKTREE_ARCHIVE_STATE_VERSION = 1, ROOT_TAB_ID = "root";
|
|
11434
|
+
var WORKTREE_META_SCHEMA_VERSION = 1, WORKTREE_ARCHIVE_STATE_VERSION = 1, OPEN_SESSIONS_STATE_VERSION = 1, ROOT_TAB_ID = "root";
|
|
11321
11435
|
|
|
11322
11436
|
// backend/src/adapters/fs.ts
|
|
11323
11437
|
import { mkdir } from "fs/promises";
|
|
11324
|
-
import { join as
|
|
11438
|
+
import { join as join9 } from "path";
|
|
11325
11439
|
function stringifyAllocatedPorts(ports) {
|
|
11326
11440
|
const entries = Object.entries(ports).map(([key, value]) => [key, String(value)]);
|
|
11327
11441
|
return Object.fromEntries(entries);
|
|
@@ -11353,25 +11467,28 @@ function parseDotenv(content) {
|
|
|
11353
11467
|
}
|
|
11354
11468
|
async function loadDotenvLocal(worktreePath) {
|
|
11355
11469
|
try {
|
|
11356
|
-
const content = await Bun.file(
|
|
11470
|
+
const content = await Bun.file(join9(worktreePath, ".env.local")).text();
|
|
11357
11471
|
return parseDotenv(content);
|
|
11358
11472
|
} catch {
|
|
11359
11473
|
return {};
|
|
11360
11474
|
}
|
|
11361
11475
|
}
|
|
11362
11476
|
function getWorktreeStoragePaths(gitDir) {
|
|
11363
|
-
const webmuxDir =
|
|
11477
|
+
const webmuxDir = join9(gitDir, "webmux");
|
|
11364
11478
|
return {
|
|
11365
11479
|
gitDir,
|
|
11366
11480
|
webmuxDir,
|
|
11367
|
-
metaPath:
|
|
11368
|
-
runtimeEnvPath:
|
|
11369
|
-
controlEnvPath:
|
|
11370
|
-
prsPath:
|
|
11481
|
+
metaPath: join9(webmuxDir, "meta.json"),
|
|
11482
|
+
runtimeEnvPath: join9(webmuxDir, "runtime.env"),
|
|
11483
|
+
controlEnvPath: join9(webmuxDir, "control.env"),
|
|
11484
|
+
prsPath: join9(webmuxDir, "prs.json")
|
|
11371
11485
|
};
|
|
11372
11486
|
}
|
|
11373
11487
|
function getProjectArchiveStatePath(gitDir) {
|
|
11374
|
-
return
|
|
11488
|
+
return join9(gitDir, "webmux", "archive.json");
|
|
11489
|
+
}
|
|
11490
|
+
function getProjectOpenSessionsStatePath(gitDir) {
|
|
11491
|
+
return join9(gitDir, "webmux", "open-sessions.json");
|
|
11375
11492
|
}
|
|
11376
11493
|
async function ensureWorktreeStorageDirs(gitDir) {
|
|
11377
11494
|
const paths = getWorktreeStoragePaths(gitDir);
|
|
@@ -11422,6 +11539,29 @@ async function writeWorktreeArchiveState(gitDir, state) {
|
|
|
11422
11539
|
await Bun.write(archivePath, JSON.stringify(state, null, 2) + `
|
|
11423
11540
|
`);
|
|
11424
11541
|
}
|
|
11542
|
+
function emptyOpenSessionsState() {
|
|
11543
|
+
return {
|
|
11544
|
+
schemaVersion: OPEN_SESSIONS_STATE_VERSION,
|
|
11545
|
+
savedAt: "",
|
|
11546
|
+
branches: []
|
|
11547
|
+
};
|
|
11548
|
+
}
|
|
11549
|
+
function isOpenSessionsState(raw) {
|
|
11550
|
+
return isRecord3(raw) && typeof raw.schemaVersion === "number" && typeof raw.savedAt === "string" && Array.isArray(raw.branches) && raw.branches.every((branch) => typeof branch === "string");
|
|
11551
|
+
}
|
|
11552
|
+
async function readOpenSessionsState(gitDir) {
|
|
11553
|
+
const statePath = getProjectOpenSessionsStatePath(gitDir);
|
|
11554
|
+
try {
|
|
11555
|
+
const raw = await Bun.file(statePath).json();
|
|
11556
|
+
return isOpenSessionsState(raw) ? {
|
|
11557
|
+
schemaVersion: raw.schemaVersion,
|
|
11558
|
+
savedAt: raw.savedAt,
|
|
11559
|
+
branches: [...raw.branches]
|
|
11560
|
+
} : emptyOpenSessionsState();
|
|
11561
|
+
} catch {
|
|
11562
|
+
return emptyOpenSessionsState();
|
|
11563
|
+
}
|
|
11564
|
+
}
|
|
11425
11565
|
function buildRuntimeEnvMap(meta, extraEnv = {}, dotenvValues = {}) {
|
|
11426
11566
|
return {
|
|
11427
11567
|
...dotenvValues,
|
|
@@ -18756,8 +18896,8 @@ var init_dist5 = __esm(() => {
|
|
|
18756
18896
|
});
|
|
18757
18897
|
|
|
18758
18898
|
// backend/src/adapters/config.ts
|
|
18759
|
-
import { readFileSync as
|
|
18760
|
-
import { dirname as
|
|
18899
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
18900
|
+
import { dirname as dirname4, join as join10, resolve as resolve7 } from "path";
|
|
18761
18901
|
function DEFAULT_ONESHOT_SYSTEM_PROMPT() {
|
|
18762
18902
|
return [
|
|
18763
18903
|
"You are running in webmux ONESHOT mode. There is NO interactive user \u2014 nobody is watching the chat or will respond to questions, approvals, or status checks. Any message asking the user to review, approve, confirm, take a look, or 'let you know' is wasted output: it will not be answered.",
|
|
@@ -18986,10 +19126,10 @@ function getDefaultProfileName(config) {
|
|
|
18986
19126
|
return Object.keys(config.profiles)[0] ?? "default";
|
|
18987
19127
|
}
|
|
18988
19128
|
function readConfigFile(root) {
|
|
18989
|
-
return
|
|
19129
|
+
return readFileSync7(join10(root, ".webmux.yaml"), "utf8");
|
|
18990
19130
|
}
|
|
18991
19131
|
function readLocalConfigFile(root) {
|
|
18992
|
-
return
|
|
19132
|
+
return readFileSync7(join10(root, ".webmux.local.yaml"), "utf8");
|
|
18993
19133
|
}
|
|
18994
19134
|
function parseConfigDocument(text) {
|
|
18995
19135
|
const parsed = $parse(text);
|
|
@@ -19137,7 +19277,7 @@ function projectRoot(dir) {
|
|
|
19137
19277
|
if (result.exitCode !== 0)
|
|
19138
19278
|
return gitRoot2(dir);
|
|
19139
19279
|
const commonDir = new TextDecoder().decode(result.stdout).trim();
|
|
19140
|
-
return commonDir ?
|
|
19280
|
+
return commonDir ? dirname4(resolve7(dir, commonDir)) : gitRoot2(dir);
|
|
19141
19281
|
}
|
|
19142
19282
|
function loadConfig(dir, options = {}) {
|
|
19143
19283
|
const root = options.resolvedRoot ? dir : projectRoot(dir);
|
|
@@ -19216,7 +19356,7 @@ var init_config = __esm(() => {
|
|
|
19216
19356
|
|
|
19217
19357
|
// backend/src/adapters/control-token.ts
|
|
19218
19358
|
import { chmod, mkdir as mkdir2 } from "fs/promises";
|
|
19219
|
-
import { dirname as
|
|
19359
|
+
import { dirname as dirname5 } from "path";
|
|
19220
19360
|
async function loadControlToken() {
|
|
19221
19361
|
if (cachedToken)
|
|
19222
19362
|
return cachedToken;
|
|
@@ -19226,7 +19366,7 @@ async function loadControlToken() {
|
|
|
19226
19366
|
return cachedToken;
|
|
19227
19367
|
}
|
|
19228
19368
|
const controlToken = crypto.randomUUID();
|
|
19229
|
-
await mkdir2(
|
|
19369
|
+
await mkdir2(dirname5(CONTROL_TOKEN_PATH), { recursive: true });
|
|
19230
19370
|
await Bun.write(CONTROL_TOKEN_PATH, controlToken);
|
|
19231
19371
|
await chmod(CONTROL_TOKEN_PATH, 384);
|
|
19232
19372
|
cachedToken = controlToken;
|
|
@@ -19494,7 +19634,7 @@ var init_docker = __esm(() => {
|
|
|
19494
19634
|
});
|
|
19495
19635
|
|
|
19496
19636
|
// backend/src/adapters/hooks.ts
|
|
19497
|
-
import { join as
|
|
19637
|
+
import { join as join11 } from "path";
|
|
19498
19638
|
function buildErrorMessage(name, exitCode, stdout2, stderr) {
|
|
19499
19639
|
const output = stderr.trim() || stdout2.trim();
|
|
19500
19640
|
if (output) {
|
|
@@ -19519,7 +19659,7 @@ class BunLifecycleHookRunner {
|
|
|
19519
19659
|
return this.direnvAvailable;
|
|
19520
19660
|
}
|
|
19521
19661
|
async buildCommand(cwd, command) {
|
|
19522
|
-
if (this.checkDirenv() && await Bun.file(
|
|
19662
|
+
if (this.checkDirenv() && await Bun.file(join11(cwd, ".envrc")).exists()) {
|
|
19523
19663
|
Bun.spawnSync(["direnv", "allow"], { cwd, stdout: "pipe", stderr: "pipe" });
|
|
19524
19664
|
return ["direnv", "exec", cwd, "bash", "-c", command];
|
|
19525
19665
|
}
|
|
@@ -19626,7 +19766,7 @@ var init_claude_cli = __esm(() => {
|
|
|
19626
19766
|
|
|
19627
19767
|
// backend/src/adapters/session-discovery.ts
|
|
19628
19768
|
import { readdir, stat as stat2 } from "fs/promises";
|
|
19629
|
-
import { basename as basename5, join as
|
|
19769
|
+
import { basename as basename5, join as join12 } from "path";
|
|
19630
19770
|
function home() {
|
|
19631
19771
|
const value = Bun.env.HOME;
|
|
19632
19772
|
if (!value)
|
|
@@ -19637,10 +19777,10 @@ function newestFirst(sessions) {
|
|
|
19637
19777
|
return sessions.sort((left, right) => right.mtimeMs - left.mtimeMs).map((entry) => entry.sessionId);
|
|
19638
19778
|
}
|
|
19639
19779
|
async function listClaudeSessionIds(cwd) {
|
|
19640
|
-
const dir =
|
|
19780
|
+
const dir = join12(home(), ".claude", "projects", encodeClaudeProjectDir(cwd));
|
|
19641
19781
|
const names = await readdir(dir).catch(() => []);
|
|
19642
19782
|
const stamped = await Promise.all(names.filter((name) => name.endsWith(".jsonl")).map(async (name) => {
|
|
19643
|
-
const info = await stat2(
|
|
19783
|
+
const info = await stat2(join12(dir, name)).catch(() => null);
|
|
19644
19784
|
return info ? { sessionId: basename5(name, ".jsonl"), mtimeMs: info.mtimeMs } : null;
|
|
19645
19785
|
}));
|
|
19646
19786
|
return newestFirst(stamped.filter((entry) => entry !== null));
|
|
@@ -19662,14 +19802,14 @@ async function readCodexSessionCwdId(path) {
|
|
|
19662
19802
|
}
|
|
19663
19803
|
}
|
|
19664
19804
|
async function listCodexSessionIds(cwd) {
|
|
19665
|
-
const root =
|
|
19805
|
+
const root = join12(home(), ".codex", "sessions");
|
|
19666
19806
|
const relPaths = await readdir(root, { recursive: true }).catch(() => []);
|
|
19667
19807
|
const rollouts = relPaths.filter((rel) => {
|
|
19668
19808
|
const name = basename5(rel);
|
|
19669
19809
|
return name.startsWith("rollout-") && name.endsWith(".jsonl");
|
|
19670
19810
|
});
|
|
19671
19811
|
const stamped = await Promise.all(rollouts.map(async (rel) => {
|
|
19672
|
-
const path =
|
|
19812
|
+
const path = join12(root, rel);
|
|
19673
19813
|
const meta = await readCodexSessionCwdId(path);
|
|
19674
19814
|
if (!meta || meta.cwd !== cwd)
|
|
19675
19815
|
return null;
|
|
@@ -19859,7 +19999,7 @@ var init_archive_state_service = __esm(() => {
|
|
|
19859
19999
|
|
|
19860
20000
|
// backend/src/adapters/agent-runtime.ts
|
|
19861
20001
|
import { chmod as chmod2, mkdir as mkdir3 } from "fs/promises";
|
|
19862
|
-
import { dirname as
|
|
20002
|
+
import { dirname as dirname6, join as join13, resolve as resolve8 } from "path";
|
|
19863
20003
|
function shellQuote(value) {
|
|
19864
20004
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
19865
20005
|
}
|
|
@@ -20278,7 +20418,7 @@ async function mergeCodexHooksFile(hooksPath, hookSettings, agentCtlPath) {
|
|
|
20278
20418
|
}
|
|
20279
20419
|
async function resolveGitCommonDir(gitDir) {
|
|
20280
20420
|
try {
|
|
20281
|
-
const commonDir = (await Bun.file(
|
|
20421
|
+
const commonDir = (await Bun.file(join13(gitDir, "commondir")).text()).trim();
|
|
20282
20422
|
if (!commonDir)
|
|
20283
20423
|
return gitDir;
|
|
20284
20424
|
return commonDir.startsWith("/") ? commonDir : resolve8(gitDir, commonDir);
|
|
@@ -20288,7 +20428,7 @@ async function resolveGitCommonDir(gitDir) {
|
|
|
20288
20428
|
}
|
|
20289
20429
|
async function ensureGeneratedCodexHooksIgnored(gitDir) {
|
|
20290
20430
|
const commonDir = await resolveGitCommonDir(gitDir);
|
|
20291
|
-
const excludePath =
|
|
20431
|
+
const excludePath = join13(commonDir, "info", "exclude");
|
|
20292
20432
|
let existing = "";
|
|
20293
20433
|
try {
|
|
20294
20434
|
existing = await Bun.file(excludePath).text();
|
|
@@ -20298,7 +20438,7 @@ async function ensureGeneratedCodexHooksIgnored(gitDir) {
|
|
|
20298
20438
|
const lines = existing.split(/\r?\n/).map((line) => line.trim());
|
|
20299
20439
|
if (lines.includes(GENERATED_CODEX_HOOKS_EXCLUDE))
|
|
20300
20440
|
return;
|
|
20301
|
-
await mkdir3(
|
|
20441
|
+
await mkdir3(dirname6(excludePath), { recursive: true });
|
|
20302
20442
|
const separator = existing.length > 0 && !existing.endsWith(`
|
|
20303
20443
|
`) ? `
|
|
20304
20444
|
` : "";
|
|
@@ -20308,12 +20448,12 @@ async function ensureGeneratedCodexHooksIgnored(gitDir) {
|
|
|
20308
20448
|
async function ensureAgentRuntimeArtifacts(input) {
|
|
20309
20449
|
const storagePaths = getWorktreeStoragePaths(input.gitDir);
|
|
20310
20450
|
const artifacts = {
|
|
20311
|
-
agentCtlPath:
|
|
20312
|
-
claudeSettingsPath:
|
|
20313
|
-
codexHooksPath:
|
|
20451
|
+
agentCtlPath: join13(storagePaths.webmuxDir, "webmux-agentctl"),
|
|
20452
|
+
claudeSettingsPath: join13(input.worktreePath, ".claude", "settings.local.json"),
|
|
20453
|
+
codexHooksPath: join13(input.worktreePath, ".codex", "hooks.json")
|
|
20314
20454
|
};
|
|
20315
|
-
await mkdir3(
|
|
20316
|
-
await mkdir3(
|
|
20455
|
+
await mkdir3(dirname6(artifacts.claudeSettingsPath), { recursive: true });
|
|
20456
|
+
await mkdir3(dirname6(artifacts.codexHooksPath), { recursive: true });
|
|
20317
20457
|
await Bun.write(artifacts.agentCtlPath, buildAgentCtlScript());
|
|
20318
20458
|
await chmod2(artifacts.agentCtlPath, 493);
|
|
20319
20459
|
const hookSettings = buildClaudeHookSettings(artifacts);
|
|
@@ -20845,7 +20985,7 @@ var init_worktree_service = __esm(() => {
|
|
|
20845
20985
|
// backend/src/services/lifecycle-service.ts
|
|
20846
20986
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
20847
20987
|
import { mkdir as mkdir4 } from "fs/promises";
|
|
20848
|
-
import { dirname as
|
|
20988
|
+
import { dirname as dirname7, resolve as resolve10 } from "path";
|
|
20849
20989
|
function toErrorMessage2(error) {
|
|
20850
20990
|
return error instanceof Error ? error.message : String(error);
|
|
20851
20991
|
}
|
|
@@ -21289,9 +21429,13 @@ class LifecycleService {
|
|
|
21289
21429
|
async pruneWorktrees() {
|
|
21290
21430
|
try {
|
|
21291
21431
|
const resolvedWorktrees = await this.resolveAllWorktrees();
|
|
21432
|
+
const sessionName = buildProjectSessionName(this.deps.projectRoot);
|
|
21292
21433
|
const removedBranches = [];
|
|
21293
21434
|
for (const resolved of resolvedWorktrees) {
|
|
21294
21435
|
const branch = resolved.entry.branch ?? resolved.entry.path;
|
|
21436
|
+
if (this.deps.tmux.hasWindow(sessionName, buildWorktreeWindowName(branch))) {
|
|
21437
|
+
continue;
|
|
21438
|
+
}
|
|
21295
21439
|
await this.removeResolvedWorktree(resolved);
|
|
21296
21440
|
removedBranches.push(branch);
|
|
21297
21441
|
}
|
|
@@ -21761,7 +21905,7 @@ ${oneshotPrompt}` : oneshotPrompt ?? baseSystemPrompt;
|
|
|
21761
21905
|
...createProgressBase,
|
|
21762
21906
|
phase: "creating_worktree"
|
|
21763
21907
|
});
|
|
21764
|
-
await mkdir4(
|
|
21908
|
+
await mkdir4(dirname7(worktreePath), { recursive: true });
|
|
21765
21909
|
initialized = await createManagedWorktree({
|
|
21766
21910
|
repoRoot: this.deps.projectRoot,
|
|
21767
21911
|
worktreePath,
|
|
@@ -22544,6 +22688,11 @@ function getWorktreeCommandUsage(command) {
|
|
|
22544
22688
|
case "prune":
|
|
22545
22689
|
return `Usage:
|
|
22546
22690
|
webmux prune`;
|
|
22691
|
+
case "restore":
|
|
22692
|
+
return `Usage:
|
|
22693
|
+
webmux restore
|
|
22694
|
+
|
|
22695
|
+
Re-open every worktree session that was open the last time sessions were saved.`;
|
|
22547
22696
|
case "tab":
|
|
22548
22697
|
return [
|
|
22549
22698
|
"Usage:",
|
|
@@ -22844,6 +22993,18 @@ function parsePruneCommandArgs(args) {
|
|
|
22844
22993
|
}
|
|
22845
22994
|
return true;
|
|
22846
22995
|
}
|
|
22996
|
+
function parseRestoreCommandArgs(args) {
|
|
22997
|
+
for (const arg of args) {
|
|
22998
|
+
if (arg === "--help" || arg === "-h") {
|
|
22999
|
+
return false;
|
|
23000
|
+
}
|
|
23001
|
+
if (arg.startsWith("-")) {
|
|
23002
|
+
throw new CommandUsageError(`Unknown option: ${arg}`);
|
|
23003
|
+
}
|
|
23004
|
+
throw new CommandUsageError(`Unexpected argument: ${arg}`);
|
|
23005
|
+
}
|
|
23006
|
+
return true;
|
|
23007
|
+
}
|
|
22847
23008
|
function parseListCommandArgs(args) {
|
|
22848
23009
|
let mode = "active";
|
|
22849
23010
|
let search = "";
|
|
@@ -22882,9 +23043,19 @@ function listProjectWorktrees(runtime) {
|
|
|
22882
23043
|
const projectDir = resolve12(runtime.projectDir);
|
|
22883
23044
|
return runtime.git.listWorktrees(projectDir).filter((entry) => !entry.bare && resolve12(entry.path) !== projectDir);
|
|
22884
23045
|
}
|
|
23046
|
+
function buildOpenWorktreeWindowSet(runtime) {
|
|
23047
|
+
const sessionName = buildProjectSessionName(resolve12(runtime.projectDir));
|
|
23048
|
+
let windows = [];
|
|
23049
|
+
try {
|
|
23050
|
+
windows = runtime.tmux.listWindows();
|
|
23051
|
+
} catch {
|
|
23052
|
+
windows = [];
|
|
23053
|
+
}
|
|
23054
|
+
return new Set(windows.filter((w) => w.sessionName === sessionName).map((w) => w.windowName));
|
|
23055
|
+
}
|
|
22885
23056
|
async function defaultConfirmPrune(worktreeCount) {
|
|
22886
23057
|
const response = await confirm({
|
|
22887
|
-
message: `Prune
|
|
23058
|
+
message: `Prune ${worktreeCount} closed worktree${worktreeCount === 1 ? "" : "s"}? This action cannot be undone.`,
|
|
22888
23059
|
initialValue: false
|
|
22889
23060
|
});
|
|
22890
23061
|
return !isCancel(response) && response;
|
|
@@ -22928,14 +23099,7 @@ async function listWorktrees(runtime, stdout2, options) {
|
|
|
22928
23099
|
stdout2("No worktrees found.");
|
|
22929
23100
|
return;
|
|
22930
23101
|
}
|
|
22931
|
-
const
|
|
22932
|
-
let windows = [];
|
|
22933
|
-
try {
|
|
22934
|
-
windows = runtime.tmux.listWindows();
|
|
22935
|
-
} catch {
|
|
22936
|
-
windows = [];
|
|
22937
|
-
}
|
|
22938
|
-
const openWindows = new Set(windows.filter((w) => w.sessionName === sessionName).map((w) => w.windowName));
|
|
23102
|
+
const openWindows = buildOpenWorktreeWindowSet(runtime);
|
|
22939
23103
|
const projectGitDir = runtime.git.resolveWorktreeGitDir(projectDir);
|
|
22940
23104
|
const archivedPaths = buildArchivedWorktreePathSet(await readWorktreeArchiveState(projectGitDir));
|
|
22941
23105
|
const rows = await Promise.all(entries.map(async (entry) => {
|
|
@@ -23000,6 +23164,7 @@ async function runWorktreeCommand(context, deps2 = {}) {
|
|
|
23000
23164
|
const resolveBaseUrl = deps2.resolveBaseUrl ?? resolveProjectBaseUrl;
|
|
23001
23165
|
const switchToTmuxWindow = deps2.switchToTmuxWindow ?? defaultSwitchToTmuxWindow;
|
|
23002
23166
|
const confirmPrune = deps2.confirmPrune ?? defaultConfirmPrune;
|
|
23167
|
+
const readOpenSessions = deps2.readOpenSessions ?? readOpenSessionsState;
|
|
23003
23168
|
try {
|
|
23004
23169
|
if (context.command === "add") {
|
|
23005
23170
|
const parsed = parseAddCommandArgs(context.args);
|
|
@@ -23078,18 +23243,85 @@ ${parsed.input.prompt}` : seed.data.conversationMarkdown;
|
|
|
23078
23243
|
stdout2("No worktrees found.");
|
|
23079
23244
|
return 0;
|
|
23080
23245
|
}
|
|
23081
|
-
|
|
23246
|
+
const openWindows = buildOpenWorktreeWindowSet(runtime2);
|
|
23247
|
+
const closedWorktrees = worktrees.filter((entry) => !openWindows.has(buildWorktreeWindowName(entry.branch ?? basename7(entry.path))));
|
|
23248
|
+
if (closedWorktrees.length === 0) {
|
|
23249
|
+
stdout2("No closed worktrees to prune.");
|
|
23250
|
+
return 0;
|
|
23251
|
+
}
|
|
23252
|
+
if (!await confirmPrune(closedWorktrees.length)) {
|
|
23082
23253
|
stdout2("Aborted.");
|
|
23083
23254
|
return 0;
|
|
23084
23255
|
}
|
|
23085
23256
|
const result = await runtime2.lifecycleService.pruneWorktrees();
|
|
23086
23257
|
if (result.removedBranches.length === 0) {
|
|
23087
|
-
stdout2("No worktrees
|
|
23258
|
+
stdout2("No closed worktrees to prune.");
|
|
23088
23259
|
return 0;
|
|
23089
23260
|
}
|
|
23090
23261
|
stdout2(`Pruned ${result.removedBranches.length} worktree${result.removedBranches.length === 1 ? "" : "s"}: ${result.removedBranches.join(", ")}`);
|
|
23091
23262
|
return 0;
|
|
23092
23263
|
}
|
|
23264
|
+
if (context.command === "restore") {
|
|
23265
|
+
if (!parseRestoreCommandArgs(context.args)) {
|
|
23266
|
+
stdout2(getWorktreeCommandUsage("restore"));
|
|
23267
|
+
return 0;
|
|
23268
|
+
}
|
|
23269
|
+
const runtime2 = createRuntime({
|
|
23270
|
+
projectDir: context.projectDir,
|
|
23271
|
+
port: context.port
|
|
23272
|
+
});
|
|
23273
|
+
const projectDir = resolve12(runtime2.projectDir);
|
|
23274
|
+
const gitDir = runtime2.git.resolveWorktreeGitDir(projectDir);
|
|
23275
|
+
const state = await readOpenSessions(gitDir);
|
|
23276
|
+
if (state.branches.length === 0) {
|
|
23277
|
+
stdout2("No saved sessions to restore.");
|
|
23278
|
+
return 0;
|
|
23279
|
+
}
|
|
23280
|
+
const sessionName = buildProjectSessionName(projectDir);
|
|
23281
|
+
let openWindows = new Set;
|
|
23282
|
+
try {
|
|
23283
|
+
openWindows = new Set(runtime2.tmux.listWindows().filter((w) => w.sessionName === sessionName).map((w) => w.windowName));
|
|
23284
|
+
} catch {
|
|
23285
|
+
openWindows = new Set;
|
|
23286
|
+
}
|
|
23287
|
+
const existingBranches = new Set(listProjectWorktrees(runtime2).map((entry) => entry.branch ?? basename7(entry.path)));
|
|
23288
|
+
let restored = 0;
|
|
23289
|
+
let skipped = 0;
|
|
23290
|
+
let failed = 0;
|
|
23291
|
+
let firstRestored = null;
|
|
23292
|
+
for (const branch2 of state.branches) {
|
|
23293
|
+
if (openWindows.has(buildWorktreeWindowName(branch2))) {
|
|
23294
|
+
stdout2(`Already open: ${branch2}`);
|
|
23295
|
+
skipped++;
|
|
23296
|
+
continue;
|
|
23297
|
+
}
|
|
23298
|
+
if (!existingBranches.has(branch2)) {
|
|
23299
|
+
stderr(`Skipping ${branch2}: worktree no longer exists`);
|
|
23300
|
+
skipped++;
|
|
23301
|
+
continue;
|
|
23302
|
+
}
|
|
23303
|
+
try {
|
|
23304
|
+
await runtime2.lifecycleService.openWorktree(branch2);
|
|
23305
|
+
stdout2(`Restored ${branch2}`);
|
|
23306
|
+
restored++;
|
|
23307
|
+
if (!firstRestored)
|
|
23308
|
+
firstRestored = branch2;
|
|
23309
|
+
} catch (error) {
|
|
23310
|
+
stderr(`Failed to restore ${branch2}: ${error instanceof Error ? error.message : String(error)}`);
|
|
23311
|
+
failed++;
|
|
23312
|
+
}
|
|
23313
|
+
}
|
|
23314
|
+
const summaryParts = [`Restored ${restored} session${restored === 1 ? "" : "s"}`];
|
|
23315
|
+
if (skipped > 0)
|
|
23316
|
+
summaryParts.push(`skipped ${skipped}`);
|
|
23317
|
+
if (failed > 0)
|
|
23318
|
+
summaryParts.push(`${failed} failed`);
|
|
23319
|
+
stdout2(`${summaryParts.join(", ")}.`);
|
|
23320
|
+
if (firstRestored) {
|
|
23321
|
+
switchToTmuxWindow(runtime2.projectDir, firstRestored);
|
|
23322
|
+
}
|
|
23323
|
+
return failed > 0 ? 1 : 0;
|
|
23324
|
+
}
|
|
23093
23325
|
if (context.command === "send") {
|
|
23094
23326
|
const parsed = parseSendCommandArgs(context.args);
|
|
23095
23327
|
if (!parsed) {
|
|
@@ -23227,13 +23459,13 @@ var init_worktree_commands = __esm(() => {
|
|
|
23227
23459
|
});
|
|
23228
23460
|
|
|
23229
23461
|
// bin/src/webmux.ts
|
|
23230
|
-
import { resolve as resolve13, dirname as
|
|
23462
|
+
import { resolve as resolve13, dirname as dirname8, join as join14 } from "path";
|
|
23231
23463
|
import { existsSync as existsSync6 } from "fs";
|
|
23232
23464
|
import { fileURLToPath } from "url";
|
|
23233
23465
|
// package.json
|
|
23234
23466
|
var package_default = {
|
|
23235
23467
|
name: "webmux",
|
|
23236
|
-
version: "0.
|
|
23468
|
+
version: "0.41.0",
|
|
23237
23469
|
description: "Web dashboard for workmux \u2014 browser UI with embedded terminals, PR monitoring, and CI integration",
|
|
23238
23470
|
type: "module",
|
|
23239
23471
|
repository: {
|
|
@@ -23290,7 +23522,7 @@ var package_default = {
|
|
|
23290
23522
|
};
|
|
23291
23523
|
|
|
23292
23524
|
// bin/src/webmux.ts
|
|
23293
|
-
var PKG_ROOT = resolve13(
|
|
23525
|
+
var PKG_ROOT = resolve13(dirname8(fileURLToPath(import.meta.url)), "..");
|
|
23294
23526
|
function usage2() {
|
|
23295
23527
|
console.log(`
|
|
23296
23528
|
webmux \u2014 Dev dashboard for managing Git worktrees
|
|
@@ -23313,7 +23545,8 @@ Usage:
|
|
|
23313
23545
|
webmux merge Merge a worktree into the main branch and remove it
|
|
23314
23546
|
webmux send Send a prompt to a running worktree agent
|
|
23315
23547
|
webmux tab List, create, switch, or close agent tabs in a worktree
|
|
23316
|
-
webmux prune Remove all worktrees in the current project
|
|
23548
|
+
webmux prune Remove all closed (not open) worktrees in the current project
|
|
23549
|
+
webmux restore Re-open all worktree sessions that were open before
|
|
23317
23550
|
webmux linear Post a worktree conversation to a Linear issue/team
|
|
23318
23551
|
webmux project List, add, or remove projects served by the dashboard
|
|
23319
23552
|
webmux completion Generate shell completion script (bash, zsh)
|
|
@@ -23331,7 +23564,7 @@ Environment:
|
|
|
23331
23564
|
`);
|
|
23332
23565
|
}
|
|
23333
23566
|
function isRootCommand(value) {
|
|
23334
|
-
return value === "serve" || value === "init" || value === "service" || value === "update" || value === "add" || value === "oneshot" || value === "list" || value === "open" || value === "close" || value === "refresh" || value === "archive" || value === "unarchive" || value === "label" || value === "remove" || value === "merge" || value === "send" || value === "tab" || value === "prune" || value === "linear" || value === "project" || value === "completion";
|
|
23567
|
+
return value === "serve" || value === "init" || value === "service" || value === "update" || value === "add" || value === "oneshot" || value === "list" || value === "open" || value === "close" || value === "refresh" || value === "archive" || value === "unarchive" || value === "label" || value === "remove" || value === "merge" || value === "send" || value === "tab" || value === "prune" || value === "restore" || value === "linear" || value === "project" || value === "completion";
|
|
23335
23568
|
}
|
|
23336
23569
|
function isServeRootOption(value) {
|
|
23337
23570
|
return value === "--port" || value === "--app" || value === "--debug" || value === "--help" || value === "-h" || value === "--version" || value === "-V";
|
|
@@ -23398,7 +23631,7 @@ Run webmux --help for usage.`);
|
|
|
23398
23631
|
};
|
|
23399
23632
|
}
|
|
23400
23633
|
function isWorktreeCommand(command) {
|
|
23401
|
-
return command === "add" || command === "list" || command === "open" || command === "close" || command === "refresh" || command === "archive" || command === "unarchive" || command === "label" || command === "remove" || command === "merge" || command === "send" || command === "tab" || command === "prune";
|
|
23634
|
+
return command === "add" || command === "list" || command === "open" || command === "close" || command === "refresh" || command === "archive" || command === "unarchive" || command === "label" || command === "remove" || command === "merge" || command === "send" || command === "tab" || command === "prune" || command === "restore";
|
|
23402
23635
|
}
|
|
23403
23636
|
async function loadEnvFile(path) {
|
|
23404
23637
|
if (!existsSync6(path))
|
|
@@ -23526,6 +23759,8 @@ Refreshing ${services.length} installed webmux service(s) to pick up the new ver
|
|
|
23526
23759
|
const parts = [];
|
|
23527
23760
|
if (outcome.regenerated)
|
|
23528
23761
|
parts.push("regenerated unit");
|
|
23762
|
+
if (outcome.migratedProject)
|
|
23763
|
+
parts.push(`migrated served project ${outcome.migratedProject}`);
|
|
23529
23764
|
if (outcome.restarted)
|
|
23530
23765
|
parts.push("restarted");
|
|
23531
23766
|
if (!outcome.regenerated && !outcome.restarted && !outcome.error)
|
|
@@ -23613,8 +23848,8 @@ Refreshing ${services.length} installed webmux service(s) to pick up the new ver
|
|
|
23613
23848
|
}
|
|
23614
23849
|
process.on("SIGINT", cleanup);
|
|
23615
23850
|
process.on("SIGTERM", cleanup);
|
|
23616
|
-
const backendEntry =
|
|
23617
|
-
const staticDir =
|
|
23851
|
+
const backendEntry = join14(PKG_ROOT, "backend", "dist", "server.js");
|
|
23852
|
+
const staticDir = join14(PKG_ROOT, "frontend", "dist");
|
|
23618
23853
|
if (!existsSync6(staticDir)) {
|
|
23619
23854
|
console.error(`Error: frontend/dist/ not found. Run 'bun run build' first.`);
|
|
23620
23855
|
process.exit(1);
|