webmux 0.18.0 → 0.19.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 +1 -1
- package/backend/dist/server.js +758 -185
- package/bin/webmux.js +205 -63
- package/frontend/dist/assets/index-CKvK6kRL.js +148 -0
- package/frontend/dist/assets/{index-Cy8MTH3L.css → index-N_4ol4EP.css} +1 -1
- package/frontend/dist/icon.svg +1 -0
- package/frontend/dist/index.html +5 -7
- package/frontend/dist/manifest.webmanifest +16 -0
- package/package.json +1 -1
- package/frontend/dist/assets/index-CzRnnD-H.js +0 -148
package/bin/webmux.js
CHANGED
|
@@ -9910,7 +9910,10 @@ function parseProjectConfig(parsed) {
|
|
|
9910
9910
|
linkedRepos: isRecord3(parsed.integrations) && isRecord3(parsed.integrations.github) ? parseLinkedRepos(parsed.integrations.github.linkedRepos) : isRecord3(parsed.integrations) && Array.isArray(parsed.integrations.github) ? parseLinkedRepos(parsed.integrations.github) : []
|
|
9911
9911
|
},
|
|
9912
9912
|
linear: {
|
|
9913
|
-
enabled: isRecord3(parsed.integrations) && isRecord3(parsed.integrations.linear) && typeof parsed.integrations.linear.enabled === "boolean" ? parsed.integrations.linear.enabled : DEFAULT_CONFIG.integrations.linear.enabled
|
|
9913
|
+
enabled: isRecord3(parsed.integrations) && isRecord3(parsed.integrations.linear) && typeof parsed.integrations.linear.enabled === "boolean" ? parsed.integrations.linear.enabled : DEFAULT_CONFIG.integrations.linear.enabled,
|
|
9914
|
+
autoCreateWorktrees: isRecord3(parsed.integrations) && isRecord3(parsed.integrations.linear) && typeof parsed.integrations.linear.autoCreateWorktrees === "boolean" ? parsed.integrations.linear.autoCreateWorktrees : DEFAULT_CONFIG.integrations.linear.autoCreateWorktrees,
|
|
9915
|
+
createTicketOption: isRecord3(parsed.integrations) && isRecord3(parsed.integrations.linear) && typeof parsed.integrations.linear.createTicketOption === "boolean" ? parsed.integrations.linear.createTicketOption : DEFAULT_CONFIG.integrations.linear.createTicketOption,
|
|
9916
|
+
...isRecord3(parsed.integrations) && isRecord3(parsed.integrations.linear) && typeof parsed.integrations.linear.teamId === "string" && parsed.integrations.linear.teamId.trim() ? { teamId: parsed.integrations.linear.teamId.trim() } : {}
|
|
9914
9917
|
}
|
|
9915
9918
|
},
|
|
9916
9919
|
lifecycleHooks: parseLifecycleHooks(parsed.lifecycleHooks),
|
|
@@ -9920,21 +9923,39 @@ function parseProjectConfig(parsed) {
|
|
|
9920
9923
|
function defaultConfig() {
|
|
9921
9924
|
return parseProjectConfig({});
|
|
9922
9925
|
}
|
|
9926
|
+
function parseLocalLinearOverlay(parsed) {
|
|
9927
|
+
if (!isRecord3(parsed.integrations))
|
|
9928
|
+
return null;
|
|
9929
|
+
const linear = parsed.integrations.linear;
|
|
9930
|
+
if (!isRecord3(linear))
|
|
9931
|
+
return null;
|
|
9932
|
+
const overlay = {};
|
|
9933
|
+
if (typeof linear.enabled === "boolean")
|
|
9934
|
+
overlay.enabled = linear.enabled;
|
|
9935
|
+
if (typeof linear.autoCreateWorktrees === "boolean")
|
|
9936
|
+
overlay.autoCreateWorktrees = linear.autoCreateWorktrees;
|
|
9937
|
+
if (typeof linear.createTicketOption === "boolean")
|
|
9938
|
+
overlay.createTicketOption = linear.createTicketOption;
|
|
9939
|
+
if (typeof linear.teamId === "string" && linear.teamId.trim())
|
|
9940
|
+
overlay.teamId = linear.teamId.trim();
|
|
9941
|
+
return Object.keys(overlay).length > 0 ? overlay : null;
|
|
9942
|
+
}
|
|
9923
9943
|
function loadLocalProjectConfigOverlay(root) {
|
|
9924
9944
|
try {
|
|
9925
9945
|
const text = readLocalConfigFile(root).trim();
|
|
9926
9946
|
if (!text) {
|
|
9927
|
-
return { worktreeRoot: null, profiles: {}, lifecycleHooks: {} };
|
|
9947
|
+
return { worktreeRoot: null, profiles: {}, lifecycleHooks: {}, linear: null };
|
|
9928
9948
|
}
|
|
9929
9949
|
const parsed = parseConfigDocument(text);
|
|
9930
9950
|
const ws = isRecord3(parsed.workspace) ? parsed.workspace : null;
|
|
9931
9951
|
return {
|
|
9932
9952
|
worktreeRoot: ws && typeof ws.worktreeRoot === "string" ? ws.worktreeRoot : null,
|
|
9933
9953
|
profiles: parseProfiles(parsed.profiles, false),
|
|
9934
|
-
lifecycleHooks: parseLifecycleHooks(parsed.lifecycleHooks)
|
|
9954
|
+
lifecycleHooks: parseLifecycleHooks(parsed.lifecycleHooks),
|
|
9955
|
+
linear: parseLocalLinearOverlay(parsed)
|
|
9935
9956
|
};
|
|
9936
9957
|
} catch {
|
|
9937
|
-
return { worktreeRoot: null, profiles: {}, lifecycleHooks: {} };
|
|
9958
|
+
return { worktreeRoot: null, profiles: {}, lifecycleHooks: {}, linear: null };
|
|
9938
9959
|
}
|
|
9939
9960
|
}
|
|
9940
9961
|
function mergeHookCommand(projectCommand, localCommand) {
|
|
@@ -9985,7 +10006,13 @@ function loadConfig(dir, options = {}) {
|
|
|
9985
10006
|
...cloneProfiles(projectConfig.profiles),
|
|
9986
10007
|
...cloneProfiles(localOverlay.profiles)
|
|
9987
10008
|
},
|
|
9988
|
-
lifecycleHooks: mergeLifecycleHooks(projectConfig.lifecycleHooks, localOverlay.lifecycleHooks)
|
|
10009
|
+
lifecycleHooks: mergeLifecycleHooks(projectConfig.lifecycleHooks, localOverlay.lifecycleHooks),
|
|
10010
|
+
...localOverlay.linear ? {
|
|
10011
|
+
integrations: {
|
|
10012
|
+
...projectConfig.integrations,
|
|
10013
|
+
linear: { ...projectConfig.integrations.linear, ...localOverlay.linear }
|
|
10014
|
+
}
|
|
10015
|
+
} : {}
|
|
9989
10016
|
};
|
|
9990
10017
|
}
|
|
9991
10018
|
function expandTemplate(template, env) {
|
|
@@ -10016,7 +10043,7 @@ var init_config = __esm(() => {
|
|
|
10016
10043
|
startupEnvs: {},
|
|
10017
10044
|
integrations: {
|
|
10018
10045
|
github: { linkedRepos: [] },
|
|
10019
|
-
linear: { enabled: true }
|
|
10046
|
+
linear: { enabled: true, autoCreateWorktrees: false, createTicketOption: false }
|
|
10020
10047
|
},
|
|
10021
10048
|
lifecycleHooks: {},
|
|
10022
10049
|
autoName: null
|
|
@@ -10360,8 +10387,8 @@ class BunLifecycleHookRunner {
|
|
|
10360
10387
|
}
|
|
10361
10388
|
async run(input) {
|
|
10362
10389
|
const cmd = await this.buildCommand(input.cwd, input.command);
|
|
10363
|
-
|
|
10364
|
-
|
|
10390
|
+
log.debug(`[hook-runner] Spawning: ${cmd.join(" ")} cwd=${input.cwd}`);
|
|
10391
|
+
log.debug(`[hook-runner] envKeys=${Object.keys(input.env).length}`);
|
|
10365
10392
|
const proc = Bun.spawn(cmd, {
|
|
10366
10393
|
cwd: input.cwd,
|
|
10367
10394
|
env: {
|
|
@@ -10376,17 +10403,19 @@ class BunLifecycleHookRunner {
|
|
|
10376
10403
|
new Response(proc.stdout).text(),
|
|
10377
10404
|
new Response(proc.stderr).text()
|
|
10378
10405
|
]);
|
|
10379
|
-
|
|
10406
|
+
log.debug(`[hook-runner] ${input.name} exitCode=${exitCode}`);
|
|
10380
10407
|
if (stdout.trim())
|
|
10381
|
-
|
|
10408
|
+
log.debug(`[hook-runner] stdout: ${stdout.trim()}`);
|
|
10382
10409
|
if (stderr.trim())
|
|
10383
|
-
|
|
10410
|
+
log.debug(`[hook-runner] stderr: ${stderr.trim()}`);
|
|
10384
10411
|
if (exitCode !== 0) {
|
|
10385
10412
|
throw new Error(buildErrorMessage(input.name, exitCode, stdout, stderr));
|
|
10386
10413
|
}
|
|
10387
10414
|
}
|
|
10388
10415
|
}
|
|
10389
|
-
var init_hooks = () => {
|
|
10416
|
+
var init_hooks = __esm(() => {
|
|
10417
|
+
init_log();
|
|
10418
|
+
});
|
|
10390
10419
|
|
|
10391
10420
|
// backend/src/adapters/port-probe.ts
|
|
10392
10421
|
class BunPortProbe {
|
|
@@ -11229,7 +11258,7 @@ class LifecycleService {
|
|
|
11229
11258
|
agent,
|
|
11230
11259
|
phase: "reconciling"
|
|
11231
11260
|
});
|
|
11232
|
-
await this.deps.reconciliation.reconcile(this.deps.projectRoot);
|
|
11261
|
+
await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
|
|
11233
11262
|
return {
|
|
11234
11263
|
branch,
|
|
11235
11264
|
worktreeId: initialized.meta.worktreeId
|
|
@@ -11264,7 +11293,7 @@ class LifecycleService {
|
|
|
11264
11293
|
worktreePath: resolved.entry.path,
|
|
11265
11294
|
launchMode
|
|
11266
11295
|
});
|
|
11267
|
-
await this.deps.reconciliation.reconcile(this.deps.projectRoot);
|
|
11296
|
+
await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
|
|
11268
11297
|
return {
|
|
11269
11298
|
branch,
|
|
11270
11299
|
worktreeId: initialized.meta.worktreeId
|
|
@@ -11277,7 +11306,7 @@ class LifecycleService {
|
|
|
11277
11306
|
try {
|
|
11278
11307
|
const resolved = await this.resolveExistingWorktree(branch);
|
|
11279
11308
|
this.deps.tmux.killWindow(buildProjectSessionName(this.deps.projectRoot), buildWorktreeWindowName(branch));
|
|
11280
|
-
await this.deps.reconciliation.reconcile(this.deps.projectRoot);
|
|
11309
|
+
await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
|
|
11281
11310
|
} catch (error) {
|
|
11282
11311
|
throw this.wrapOperationError(error);
|
|
11283
11312
|
}
|
|
@@ -11603,15 +11632,15 @@ class LifecycleService {
|
|
|
11603
11632
|
deleteBranch: true,
|
|
11604
11633
|
deleteBranchForce: true
|
|
11605
11634
|
}, this.deps.git);
|
|
11606
|
-
await this.deps.reconciliation.reconcile(this.deps.projectRoot);
|
|
11635
|
+
await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
|
|
11607
11636
|
}
|
|
11608
11637
|
async runLifecycleHook(input) {
|
|
11609
|
-
|
|
11638
|
+
log.debug(`[lifecycle-hook] name=${input.name} command=${input.command ?? "UNDEFINED"} meta=${input.meta ? "present" : "NULL"} cwd=${input.worktreePath}`);
|
|
11610
11639
|
if (!input.command || !input.meta) {
|
|
11611
|
-
|
|
11640
|
+
log.debug(`[lifecycle-hook] SKIPPING ${input.name}: command=${!!input.command} meta=${!!input.meta}`);
|
|
11612
11641
|
return;
|
|
11613
11642
|
}
|
|
11614
|
-
|
|
11643
|
+
log.debug(`[lifecycle-hook] RUNNING ${input.name}: ${input.command} in ${input.worktreePath}`);
|
|
11615
11644
|
const dotenvValues = await loadDotenvLocal(input.worktreePath);
|
|
11616
11645
|
await this.deps.hooks.run({
|
|
11617
11646
|
name: input.name,
|
|
@@ -11621,7 +11650,7 @@ class LifecycleService {
|
|
|
11621
11650
|
WEBMUX_WORKTREE_PATH: input.worktreePath
|
|
11622
11651
|
}, dotenvValues)
|
|
11623
11652
|
});
|
|
11624
|
-
|
|
11653
|
+
log.debug(`[lifecycle-hook] COMPLETED ${input.name}`);
|
|
11625
11654
|
}
|
|
11626
11655
|
async reportCreateProgress(progress) {
|
|
11627
11656
|
await this.deps.onCreateProgress?.(progress);
|
|
@@ -11645,6 +11674,7 @@ var init_lifecycle_service = __esm(() => {
|
|
|
11645
11674
|
init_policies();
|
|
11646
11675
|
init_session_service();
|
|
11647
11676
|
init_worktree_service();
|
|
11677
|
+
init_log();
|
|
11648
11678
|
LifecycleError = class LifecycleError extends Error {
|
|
11649
11679
|
status;
|
|
11650
11680
|
constructor(message, status2) {
|
|
@@ -11925,6 +11955,21 @@ var init_project_runtime = __esm(() => {
|
|
|
11925
11955
|
init_tmux();
|
|
11926
11956
|
});
|
|
11927
11957
|
|
|
11958
|
+
// backend/src/lib/async.ts
|
|
11959
|
+
async function mapWithConcurrency(items, limit, fn) {
|
|
11960
|
+
const results = new Array(items.length);
|
|
11961
|
+
let next = 0;
|
|
11962
|
+
const concurrency = Math.max(1, Math.min(limit, items.length || 1));
|
|
11963
|
+
async function worker() {
|
|
11964
|
+
while (next < items.length) {
|
|
11965
|
+
const index = next++;
|
|
11966
|
+
results[index] = await fn(items[index]);
|
|
11967
|
+
}
|
|
11968
|
+
}
|
|
11969
|
+
await Promise.all(Array.from({ length: concurrency }, () => worker()));
|
|
11970
|
+
return results;
|
|
11971
|
+
}
|
|
11972
|
+
|
|
11928
11973
|
// backend/src/services/reconciliation-service.ts
|
|
11929
11974
|
import { basename as basename4, resolve as resolve6 } from "path";
|
|
11930
11975
|
function makeUnmanagedWorktreeId(path) {
|
|
@@ -11967,11 +12012,34 @@ function resolveBranch(entry, metaBranch) {
|
|
|
11967
12012
|
|
|
11968
12013
|
class ReconciliationService {
|
|
11969
12014
|
deps;
|
|
11970
|
-
|
|
12015
|
+
freshnessMs;
|
|
12016
|
+
now;
|
|
12017
|
+
concurrency;
|
|
12018
|
+
inFlight = null;
|
|
12019
|
+
lastReconciledAt = 0;
|
|
12020
|
+
constructor(deps2, options = {}) {
|
|
11971
12021
|
this.deps = deps2;
|
|
12022
|
+
this.freshnessMs = options.freshnessMs ?? 500;
|
|
12023
|
+
this.now = options.now ?? Date.now;
|
|
12024
|
+
this.concurrency = options.concurrency ?? 4;
|
|
11972
12025
|
}
|
|
11973
|
-
async reconcile(repoRoot) {
|
|
12026
|
+
async reconcile(repoRoot, options = {}) {
|
|
12027
|
+
if (this.inFlight) {
|
|
12028
|
+
return await this.inFlight;
|
|
12029
|
+
}
|
|
12030
|
+
if (!options.force && this.now() - this.lastReconciledAt < this.freshnessMs) {
|
|
12031
|
+
return;
|
|
12032
|
+
}
|
|
11974
12033
|
const normalizedRepoRoot = resolve6(repoRoot);
|
|
12034
|
+
const reconcilePromise = this.runReconcile(normalizedRepoRoot).then(() => {
|
|
12035
|
+
this.lastReconciledAt = this.now();
|
|
12036
|
+
});
|
|
12037
|
+
this.inFlight = reconcilePromise.finally(() => {
|
|
12038
|
+
this.inFlight = null;
|
|
12039
|
+
});
|
|
12040
|
+
return await this.inFlight;
|
|
12041
|
+
}
|
|
12042
|
+
async runReconcile(normalizedRepoRoot) {
|
|
11975
12043
|
const worktrees = this.deps.git.listWorktrees(normalizedRepoRoot);
|
|
11976
12044
|
const sessionName = buildProjectSessionName(normalizedRepoRoot);
|
|
11977
12045
|
let windows = [];
|
|
@@ -11981,40 +12049,32 @@ class ReconciliationService {
|
|
|
11981
12049
|
windows = [];
|
|
11982
12050
|
}
|
|
11983
12051
|
const seenWorktreeIds = new Set;
|
|
11984
|
-
|
|
11985
|
-
|
|
11986
|
-
continue;
|
|
11987
|
-
if (resolve6(entry.path) === normalizedRepoRoot)
|
|
11988
|
-
continue;
|
|
12052
|
+
const candidateEntries = worktrees.filter((entry) => !entry.bare && resolve6(entry.path) !== normalizedRepoRoot);
|
|
12053
|
+
const reconciledStates = await mapWithConcurrency(candidateEntries, this.concurrency, async (entry) => {
|
|
11989
12054
|
const gitDir = this.deps.git.resolveWorktreeGitDir(entry.path);
|
|
11990
12055
|
const meta = await readWorktreeMeta(gitDir);
|
|
11991
12056
|
const branch = resolveBranch(entry, meta?.branch ?? null);
|
|
11992
12057
|
const worktreeId = meta?.worktreeId ?? makeUnmanagedWorktreeId(entry.path);
|
|
11993
|
-
|
|
11994
|
-
|
|
12058
|
+
const gitStatus = this.deps.git.readWorktreeStatus(entry.path);
|
|
12059
|
+
const window = findWindow(windows, sessionName, branch);
|
|
12060
|
+
return {
|
|
11995
12061
|
worktreeId,
|
|
11996
12062
|
branch,
|
|
11997
12063
|
path: entry.path,
|
|
11998
12064
|
profile: meta?.profile ?? null,
|
|
11999
12065
|
agentName: meta?.agent ?? null,
|
|
12000
|
-
runtime: meta?.runtime ?? "host"
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
exists: window !== null,
|
|
12013
|
-
sessionName: window?.sessionName ?? null,
|
|
12014
|
-
paneCount: window?.paneCount ?? 0
|
|
12015
|
-
});
|
|
12016
|
-
if (meta) {
|
|
12017
|
-
this.deps.runtime.setServices(worktreeId, await buildServiceStates(this.deps, {
|
|
12066
|
+
runtime: meta?.runtime ?? "host",
|
|
12067
|
+
git: {
|
|
12068
|
+
dirty: gitStatus.dirty,
|
|
12069
|
+
aheadCount: gitStatus.aheadCount,
|
|
12070
|
+
currentCommit: gitStatus.currentCommit
|
|
12071
|
+
},
|
|
12072
|
+
session: {
|
|
12073
|
+
exists: window !== null,
|
|
12074
|
+
sessionName: window?.sessionName ?? null,
|
|
12075
|
+
paneCount: window?.paneCount ?? 0
|
|
12076
|
+
},
|
|
12077
|
+
services: meta ? await buildServiceStates(this.deps, {
|
|
12018
12078
|
allocatedPorts: meta.allocatedPorts,
|
|
12019
12079
|
startupEnvValues: meta.startupEnvValues,
|
|
12020
12080
|
worktreeId: meta.worktreeId,
|
|
@@ -12022,11 +12082,34 @@ class ReconciliationService {
|
|
|
12022
12082
|
profile: meta.profile,
|
|
12023
12083
|
agent: meta.agent,
|
|
12024
12084
|
runtime: meta.runtime
|
|
12025
|
-
})
|
|
12026
|
-
|
|
12027
|
-
|
|
12028
|
-
|
|
12029
|
-
|
|
12085
|
+
}) : [],
|
|
12086
|
+
prs: await readWorktreePrs(gitDir)
|
|
12087
|
+
};
|
|
12088
|
+
});
|
|
12089
|
+
for (const state of reconciledStates) {
|
|
12090
|
+
seenWorktreeIds.add(state.worktreeId);
|
|
12091
|
+
this.deps.runtime.upsertWorktree({
|
|
12092
|
+
worktreeId: state.worktreeId,
|
|
12093
|
+
branch: state.branch,
|
|
12094
|
+
path: state.path,
|
|
12095
|
+
profile: state.profile,
|
|
12096
|
+
agentName: state.agentName,
|
|
12097
|
+
runtime: state.runtime
|
|
12098
|
+
});
|
|
12099
|
+
this.deps.runtime.setGitState(state.worktreeId, {
|
|
12100
|
+
exists: true,
|
|
12101
|
+
branch: state.branch,
|
|
12102
|
+
dirty: state.git.dirty,
|
|
12103
|
+
aheadCount: state.git.aheadCount,
|
|
12104
|
+
currentCommit: state.git.currentCommit
|
|
12105
|
+
});
|
|
12106
|
+
this.deps.runtime.setSessionState(state.worktreeId, {
|
|
12107
|
+
exists: state.session.exists,
|
|
12108
|
+
sessionName: state.session.sessionName,
|
|
12109
|
+
paneCount: state.session.paneCount
|
|
12110
|
+
});
|
|
12111
|
+
this.deps.runtime.setServices(state.worktreeId, state.services);
|
|
12112
|
+
this.deps.runtime.setPrs(state.worktreeId, state.prs);
|
|
12030
12113
|
}
|
|
12031
12114
|
for (const state of this.deps.runtime.listWorktrees()) {
|
|
12032
12115
|
if (!seenWorktreeIds.has(state.worktreeId)) {
|
|
@@ -12148,13 +12231,14 @@ function getWorktreeCommandUsage(command) {
|
|
|
12148
12231
|
case "add":
|
|
12149
12232
|
return [
|
|
12150
12233
|
"Usage:",
|
|
12151
|
-
" webmux add [branch] [--profile <name>] [--agent <claude|codex>] [--prompt <text>] [--env KEY=VALUE]",
|
|
12234
|
+
" webmux add [branch] [--profile <name>] [--agent <claude|codex>] [--prompt <text>] [--env KEY=VALUE] [--detach]",
|
|
12152
12235
|
"",
|
|
12153
12236
|
"Options:",
|
|
12154
12237
|
" --profile <name> Worktree profile from .webmux.yaml",
|
|
12155
12238
|
" --agent <claude|codex> Agent to launch in the worktree",
|
|
12156
12239
|
" --prompt <text> Initial agent prompt",
|
|
12157
12240
|
" --env KEY=VALUE Runtime env override (repeatable)",
|
|
12241
|
+
" -d, --detach Create worktree without switching to it",
|
|
12158
12242
|
" --help Show this help message"
|
|
12159
12243
|
].join(`
|
|
12160
12244
|
`);
|
|
@@ -12208,6 +12292,7 @@ function parseAgent(value) {
|
|
|
12208
12292
|
function parseAddCommandArgs(args) {
|
|
12209
12293
|
const input = {};
|
|
12210
12294
|
const envOverrides = {};
|
|
12295
|
+
let detach = false;
|
|
12211
12296
|
for (let index = 0;index < args.length; index++) {
|
|
12212
12297
|
const arg = args[index];
|
|
12213
12298
|
if (!arg)
|
|
@@ -12215,6 +12300,10 @@ function parseAddCommandArgs(args) {
|
|
|
12215
12300
|
if (arg === "--help" || arg === "-h") {
|
|
12216
12301
|
return null;
|
|
12217
12302
|
}
|
|
12303
|
+
if (arg === "--detach" || arg === "-d") {
|
|
12304
|
+
detach = true;
|
|
12305
|
+
continue;
|
|
12306
|
+
}
|
|
12218
12307
|
if (arg === "--profile" || arg.startsWith("--profile=")) {
|
|
12219
12308
|
const { value, nextIndex } = readOptionValue(args, index, "--profile");
|
|
12220
12309
|
input.profile = value;
|
|
@@ -12254,7 +12343,7 @@ function parseAddCommandArgs(args) {
|
|
|
12254
12343
|
if (Object.keys(envOverrides).length > 0) {
|
|
12255
12344
|
input.envOverrides = envOverrides;
|
|
12256
12345
|
}
|
|
12257
|
-
return input;
|
|
12346
|
+
return { input, detach };
|
|
12258
12347
|
}
|
|
12259
12348
|
function parseBranchCommandArgs(args) {
|
|
12260
12349
|
let branch = null;
|
|
@@ -12368,8 +12457,8 @@ async function runWorktreeCommand(context, deps2 = {}) {
|
|
|
12368
12457
|
const confirmPrune = deps2.confirmPrune ?? defaultConfirmPrune;
|
|
12369
12458
|
try {
|
|
12370
12459
|
if (context.command === "add") {
|
|
12371
|
-
const
|
|
12372
|
-
if (!
|
|
12460
|
+
const parsed = parseAddCommandArgs(context.args);
|
|
12461
|
+
if (!parsed) {
|
|
12373
12462
|
stdout(getWorktreeCommandUsage("add"));
|
|
12374
12463
|
return 0;
|
|
12375
12464
|
}
|
|
@@ -12377,9 +12466,11 @@ async function runWorktreeCommand(context, deps2 = {}) {
|
|
|
12377
12466
|
projectDir: context.projectDir,
|
|
12378
12467
|
port: context.port
|
|
12379
12468
|
});
|
|
12380
|
-
const result = await runtime2.lifecycleService.createWorktree(input);
|
|
12469
|
+
const result = await runtime2.lifecycleService.createWorktree(parsed.input);
|
|
12381
12470
|
stdout(`Created worktree ${result.branch}`);
|
|
12382
|
-
|
|
12471
|
+
if (!parsed.detach) {
|
|
12472
|
+
switchToTmuxWindow(runtime2.projectDir, result.branch);
|
|
12473
|
+
}
|
|
12383
12474
|
return 0;
|
|
12384
12475
|
}
|
|
12385
12476
|
if (context.command === "list") {
|
|
@@ -12472,7 +12563,7 @@ import { fileURLToPath } from "url";
|
|
|
12472
12563
|
// package.json
|
|
12473
12564
|
var package_default = {
|
|
12474
12565
|
name: "webmux",
|
|
12475
|
-
version: "0.
|
|
12566
|
+
version: "0.19.0",
|
|
12476
12567
|
description: "Web dashboard for workmux \u2014 browser UI with embedded terminals, PR monitoring, and CI integration",
|
|
12477
12568
|
type: "module",
|
|
12478
12569
|
repository: {
|
|
@@ -12532,7 +12623,7 @@ function usage2() {
|
|
|
12532
12623
|
webmux \u2014 Dev dashboard for managing Git worktrees
|
|
12533
12624
|
|
|
12534
12625
|
Usage:
|
|
12535
|
-
webmux serve Start the dashboard server
|
|
12626
|
+
webmux serve Start the dashboard server (--app opens in app mode)
|
|
12536
12627
|
webmux init Interactive project setup
|
|
12537
12628
|
webmux service Manage webmux as a system service
|
|
12538
12629
|
webmux update Update webmux to the latest version
|
|
@@ -12547,6 +12638,7 @@ Usage:
|
|
|
12547
12638
|
|
|
12548
12639
|
Options:
|
|
12549
12640
|
--port N Set port (default: 5111)
|
|
12641
|
+
--app Open dashboard in browser app mode (minimal window)
|
|
12550
12642
|
--debug Show debug-level logs
|
|
12551
12643
|
--version Show version number
|
|
12552
12644
|
--help Show this help message
|
|
@@ -12559,11 +12651,12 @@ function isRootCommand(value) {
|
|
|
12559
12651
|
return value === "serve" || value === "init" || value === "service" || value === "update" || value === "add" || value === "list" || value === "open" || value === "close" || value === "remove" || value === "merge" || value === "prune" || value === "completion";
|
|
12560
12652
|
}
|
|
12561
12653
|
function isServeRootOption(value) {
|
|
12562
|
-
return value === "--port" || value === "--debug" || value === "--help" || value === "-h" || value === "--version" || value === "-V";
|
|
12654
|
+
return value === "--port" || value === "--app" || value === "--debug" || value === "--help" || value === "-h" || value === "--version" || value === "-V";
|
|
12563
12655
|
}
|
|
12564
12656
|
function parseRootArgs(args) {
|
|
12565
12657
|
let port = parseInt(process.env.PORT || "5111", 10);
|
|
12566
12658
|
let debug = false;
|
|
12659
|
+
let app = false;
|
|
12567
12660
|
let command = null;
|
|
12568
12661
|
const commandArgs = [];
|
|
12569
12662
|
for (let index = 0;index < args.length; index++) {
|
|
@@ -12587,6 +12680,9 @@ function parseRootArgs(args) {
|
|
|
12587
12680
|
index += 1;
|
|
12588
12681
|
break;
|
|
12589
12682
|
}
|
|
12683
|
+
case "--app":
|
|
12684
|
+
app = true;
|
|
12685
|
+
break;
|
|
12590
12686
|
case "--debug":
|
|
12591
12687
|
debug = true;
|
|
12592
12688
|
break;
|
|
@@ -12610,6 +12706,7 @@ Run webmux --help for usage.`);
|
|
|
12610
12706
|
return {
|
|
12611
12707
|
port,
|
|
12612
12708
|
debug,
|
|
12709
|
+
app,
|
|
12613
12710
|
command,
|
|
12614
12711
|
commandArgs
|
|
12615
12712
|
};
|
|
@@ -12636,10 +12733,44 @@ async function loadEnvFile(path) {
|
|
|
12636
12733
|
}
|
|
12637
12734
|
}
|
|
12638
12735
|
}
|
|
12639
|
-
function
|
|
12736
|
+
function findBrowserBinary() {
|
|
12737
|
+
const candidates = process.platform === "darwin" ? [
|
|
12738
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
12739
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
12740
|
+
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
12741
|
+
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
12742
|
+
] : [
|
|
12743
|
+
"google-chrome",
|
|
12744
|
+
"google-chrome-stable",
|
|
12745
|
+
"chromium",
|
|
12746
|
+
"chromium-browser",
|
|
12747
|
+
"microsoft-edge",
|
|
12748
|
+
"brave-browser"
|
|
12749
|
+
];
|
|
12750
|
+
for (const candidate of candidates) {
|
|
12751
|
+
const found = candidate.startsWith("/") ? existsSync5(candidate) : Bun.spawnSync(["which", candidate], { stdout: "pipe", stderr: "pipe" }).success;
|
|
12752
|
+
if (found)
|
|
12753
|
+
return candidate;
|
|
12754
|
+
}
|
|
12755
|
+
return null;
|
|
12756
|
+
}
|
|
12757
|
+
function openAppMode(url) {
|
|
12758
|
+
const browser = findBrowserBinary();
|
|
12759
|
+
if (!browser) {
|
|
12760
|
+
console.log(`[app] No Chromium-based browser found \u2014 open ${url} manually`);
|
|
12761
|
+
return;
|
|
12762
|
+
}
|
|
12763
|
+
console.log(`[app] Opening ${url} in app mode`);
|
|
12764
|
+
Bun.spawn([browser, `--app=${url}`], {
|
|
12765
|
+
stdout: "ignore",
|
|
12766
|
+
stderr: "ignore"
|
|
12767
|
+
});
|
|
12768
|
+
}
|
|
12769
|
+
function pipeWithPrefix(stream, prefix, onTrigger) {
|
|
12640
12770
|
const reader = stream.getReader();
|
|
12641
12771
|
const decoder = new TextDecoder;
|
|
12642
12772
|
let buffer = "";
|
|
12773
|
+
let fired = false;
|
|
12643
12774
|
(async () => {
|
|
12644
12775
|
while (true) {
|
|
12645
12776
|
const { done, value } = await reader.read();
|
|
@@ -12651,6 +12782,10 @@ function pipeWithPrefix(stream, prefix) {
|
|
|
12651
12782
|
buffer = lines.pop();
|
|
12652
12783
|
for (const line of lines) {
|
|
12653
12784
|
console.log(`${prefix} ${line}`);
|
|
12785
|
+
if (onTrigger && !fired && line.includes(onTrigger.text)) {
|
|
12786
|
+
fired = true;
|
|
12787
|
+
onTrigger.callback();
|
|
12788
|
+
}
|
|
12654
12789
|
}
|
|
12655
12790
|
}
|
|
12656
12791
|
if (buffer) {
|
|
@@ -12755,7 +12890,14 @@ async function main(args = process.argv.slice(2)) {
|
|
|
12755
12890
|
stderr: "pipe"
|
|
12756
12891
|
});
|
|
12757
12892
|
children.push(be);
|
|
12758
|
-
|
|
12893
|
+
if (parsed.app) {
|
|
12894
|
+
pipeWithPrefix(be.stdout, "[BE]", {
|
|
12895
|
+
text: "Dev Dashboard API running at",
|
|
12896
|
+
callback: () => openAppMode(`http://localhost:${parsed.port}`)
|
|
12897
|
+
});
|
|
12898
|
+
} else {
|
|
12899
|
+
pipeWithPrefix(be.stdout, "[BE]");
|
|
12900
|
+
}
|
|
12759
12901
|
pipeWithPrefix(be.stderr, "[BE]");
|
|
12760
12902
|
await be.exited;
|
|
12761
12903
|
}
|