webmux 0.22.1 → 0.24.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 +203 -119
- package/bin/webmux.js +195 -112
- package/frontend/dist/assets/index-BRoz1T_W.js +151 -0
- package/frontend/dist/assets/{index-Dxe_PLr0.css → index-D9R5ycW2.css} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-BukbebAA.js +0 -150
package/bin/webmux.js
CHANGED
|
@@ -257,6 +257,9 @@ class BunGitGateway {
|
|
|
257
257
|
readWorktreeStatus(cwd) {
|
|
258
258
|
return readGitWorktreeStatus(cwd);
|
|
259
259
|
}
|
|
260
|
+
readStatus(cwd) {
|
|
261
|
+
return runGit(["status", "--short", "--untracked-files=all"], cwd);
|
|
262
|
+
}
|
|
260
263
|
createWorktree(opts) {
|
|
261
264
|
const args = ["worktree", "add"];
|
|
262
265
|
if (opts.mode === "new") {
|
|
@@ -10762,11 +10765,12 @@ function buildClaudeArgs(model, systemPrompt, prompt) {
|
|
|
10762
10765
|
systemPrompt,
|
|
10763
10766
|
"--output-format",
|
|
10764
10767
|
"text",
|
|
10765
|
-
"--no-session-persistence"
|
|
10768
|
+
"--no-session-persistence",
|
|
10769
|
+
"--model",
|
|
10770
|
+
model || DEFAULT_AUTO_NAME_MODEL,
|
|
10771
|
+
"--max-tokens",
|
|
10772
|
+
String(AUTO_NAME_MAX_TOKENS)
|
|
10766
10773
|
];
|
|
10767
|
-
if (model) {
|
|
10768
|
-
args.push("--model", model);
|
|
10769
|
-
}
|
|
10770
10774
|
args.push(prompt);
|
|
10771
10775
|
return args;
|
|
10772
10776
|
}
|
|
@@ -10812,8 +10816,11 @@ class AutoNameService {
|
|
|
10812
10816
|
throw new Error(`'${cli}' CLI not found. Install it or check your PATH.`);
|
|
10813
10817
|
}
|
|
10814
10818
|
if (result.exitCode !== 0) {
|
|
10815
|
-
const
|
|
10816
|
-
|
|
10819
|
+
const stderr = result.stderr.trim();
|
|
10820
|
+
const stdout = result.stdout.trim();
|
|
10821
|
+
const output2 = stderr || stdout || `exit ${result.exitCode}`;
|
|
10822
|
+
const command = args.join(" ");
|
|
10823
|
+
throw new Error(`${cli} failed (command: ${command}): ${output2}`);
|
|
10817
10824
|
}
|
|
10818
10825
|
const output = result.stdout.trim();
|
|
10819
10826
|
if (!output) {
|
|
@@ -10822,7 +10829,7 @@ class AutoNameService {
|
|
|
10822
10829
|
return normalizeGeneratedBranchName(output);
|
|
10823
10830
|
}
|
|
10824
10831
|
}
|
|
10825
|
-
var MAX_BRANCH_LENGTH = 40, DEFAULT_SYSTEM_PROMPT;
|
|
10832
|
+
var MAX_BRANCH_LENGTH = 40, DEFAULT_AUTO_NAME_MODEL = "claude-haiku-4-5-20251001", AUTO_NAME_MAX_TOKENS = 50, DEFAULT_SYSTEM_PROMPT;
|
|
10826
10833
|
var init_auto_name_service = __esm(() => {
|
|
10827
10834
|
init_policies();
|
|
10828
10835
|
DEFAULT_SYSTEM_PROMPT = [
|
|
@@ -11131,7 +11138,7 @@ function buildAgentInvocation(input) {
|
|
|
11131
11138
|
if (input.launchMode === "resume") {
|
|
11132
11139
|
return `codex${yoloFlag2} resume --last`;
|
|
11133
11140
|
}
|
|
11134
|
-
const promptSuffix2 = input.prompt ? ` ${quoteShell(input.prompt)}` : "";
|
|
11141
|
+
const promptSuffix2 = input.prompt ? ` -- ${quoteShell(input.prompt)}` : "";
|
|
11135
11142
|
if (input.systemPrompt) {
|
|
11136
11143
|
return `codex${yoloFlag2} -c ${quoteShell(`developer_instructions=${input.systemPrompt}`)}${promptSuffix2}`;
|
|
11137
11144
|
}
|
|
@@ -11141,7 +11148,7 @@ function buildAgentInvocation(input) {
|
|
|
11141
11148
|
if (input.launchMode === "resume") {
|
|
11142
11149
|
return `claude${yoloFlag} --continue`;
|
|
11143
11150
|
}
|
|
11144
|
-
const promptSuffix = input.prompt ? ` ${quoteShell(input.prompt)}` : "";
|
|
11151
|
+
const promptSuffix = input.prompt ? ` -- ${quoteShell(input.prompt)}` : "";
|
|
11145
11152
|
if (input.systemPrompt) {
|
|
11146
11153
|
return `claude${yoloFlag} --append-system-prompt ${quoteShell(input.systemPrompt)}${promptSuffix}`;
|
|
11147
11154
|
}
|
|
@@ -11425,120 +11432,65 @@ function toErrorMessage2(error) {
|
|
|
11425
11432
|
function stringifyStartupEnvValue(value) {
|
|
11426
11433
|
return typeof value === "boolean" ? String(value) : value;
|
|
11427
11434
|
}
|
|
11435
|
+
function prefixAgentBranch(agent, branch) {
|
|
11436
|
+
return `${agent}-${branch}`;
|
|
11437
|
+
}
|
|
11438
|
+
function buildCreateWorktreeTargets(branch, agentSelection) {
|
|
11439
|
+
if (agentSelection === "both") {
|
|
11440
|
+
return [
|
|
11441
|
+
{ branch: prefixAgentBranch("claude", branch), agent: "claude" },
|
|
11442
|
+
{ branch: prefixAgentBranch("codex", branch), agent: "codex" }
|
|
11443
|
+
];
|
|
11444
|
+
}
|
|
11445
|
+
return [{ branch, agent: agentSelection }];
|
|
11446
|
+
}
|
|
11428
11447
|
|
|
11429
11448
|
class LifecycleService {
|
|
11430
11449
|
deps;
|
|
11431
11450
|
constructor(deps2) {
|
|
11432
11451
|
this.deps = deps2;
|
|
11433
11452
|
}
|
|
11434
|
-
async
|
|
11453
|
+
async createWorktrees(input) {
|
|
11435
11454
|
const mode = input.mode ?? "new";
|
|
11436
|
-
const
|
|
11437
|
-
if (
|
|
11438
|
-
throw new LifecycleError("
|
|
11439
|
-
}
|
|
11440
|
-
if (requestedBaseBranch && mode === "existing") {
|
|
11441
|
-
throw new LifecycleError("Base branch is only supported for new worktrees", 400);
|
|
11455
|
+
const agentSelection = input.agent ?? this.deps.config.workspace.defaultAgent;
|
|
11456
|
+
if (agentSelection === "both" && mode === "existing") {
|
|
11457
|
+
throw new LifecycleError("Creating both agents is only supported for new worktrees", 400);
|
|
11442
11458
|
}
|
|
11443
11459
|
const branch = await this.resolveBranch(input.branch, input.prompt, mode);
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
}
|
|
11447
|
-
const baseBranch = mode === "new" ? requestedBaseBranch || this.deps.config.workspace.mainBranch : undefined;
|
|
11448
|
-
const branchAvailability = this.resolveBranchAvailability(branch, mode);
|
|
11449
|
-
const { profileName, profile } = this.resolveProfile(input.profile);
|
|
11450
|
-
const agent = this.resolveAgent(input.agent);
|
|
11451
|
-
const worktreePath = this.resolveWorktreePath(branch);
|
|
11452
|
-
const createProgressBase = {
|
|
11453
|
-
branch,
|
|
11454
|
-
...baseBranch ? { baseBranch } : {},
|
|
11455
|
-
path: worktreePath,
|
|
11456
|
-
profile: profileName,
|
|
11457
|
-
agent
|
|
11458
|
-
};
|
|
11459
|
-
const deleteBranchOnRollback = mode === "new" || branchAvailability.deleteBranchOnRollback;
|
|
11460
|
-
let initialized = null;
|
|
11460
|
+
const targets = buildCreateWorktreeTargets(branch, agentSelection);
|
|
11461
|
+
const createdBranches = [];
|
|
11461
11462
|
try {
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
11467
|
-
|
|
11468
|
-
|
|
11469
|
-
|
|
11470
|
-
|
|
11471
|
-
mode,
|
|
11472
|
-
...baseBranch ? { baseBranch } : {},
|
|
11473
|
-
...branchAvailability.startPoint ? { startPoint: branchAvailability.startPoint } : {},
|
|
11474
|
-
profile: profileName,
|
|
11475
|
-
agent,
|
|
11476
|
-
runtime: profile.runtime,
|
|
11477
|
-
startupEnvValues: await this.buildStartupEnvValues(input.envOverrides),
|
|
11478
|
-
allocatedPorts: await this.allocatePorts(),
|
|
11479
|
-
runtimeEnvExtras: { WEBMUX_WORKTREE_PATH: worktreePath },
|
|
11480
|
-
controlUrl: this.controlUrl(),
|
|
11481
|
-
controlToken: await this.deps.getControlToken(),
|
|
11482
|
-
deleteBranchOnRollback
|
|
11483
|
-
}, {
|
|
11484
|
-
git: this.deps.git
|
|
11485
|
-
});
|
|
11486
|
-
await this.reportCreateProgress({
|
|
11487
|
-
...createProgressBase,
|
|
11488
|
-
phase: "running_post_create_hook"
|
|
11489
|
-
});
|
|
11490
|
-
await this.runLifecycleHook({
|
|
11491
|
-
name: "postCreate",
|
|
11492
|
-
command: this.deps.config.lifecycleHooks.postCreate,
|
|
11493
|
-
meta: initialized.meta,
|
|
11494
|
-
worktreePath
|
|
11495
|
-
});
|
|
11496
|
-
initialized = await this.refreshManagedArtifactsFromMeta({
|
|
11497
|
-
gitDir: initialized.paths.gitDir,
|
|
11498
|
-
meta: initialized.meta,
|
|
11499
|
-
worktreePath
|
|
11500
|
-
});
|
|
11501
|
-
await this.reportCreateProgress({
|
|
11502
|
-
...createProgressBase,
|
|
11503
|
-
phase: "preparing_runtime"
|
|
11504
|
-
});
|
|
11505
|
-
await ensureAgentRuntimeArtifacts({
|
|
11506
|
-
gitDir: initialized.paths.gitDir,
|
|
11507
|
-
worktreePath
|
|
11508
|
-
});
|
|
11509
|
-
await this.reportCreateProgress({
|
|
11510
|
-
...createProgressBase,
|
|
11511
|
-
phase: "starting_session"
|
|
11512
|
-
});
|
|
11513
|
-
await this.materializeRuntimeSession({
|
|
11514
|
-
branch,
|
|
11515
|
-
profile,
|
|
11516
|
-
agent,
|
|
11517
|
-
initialized,
|
|
11518
|
-
worktreePath,
|
|
11519
|
-
prompt: input.prompt,
|
|
11520
|
-
launchMode: "fresh"
|
|
11521
|
-
});
|
|
11522
|
-
await this.reportCreateProgress({
|
|
11523
|
-
...createProgressBase,
|
|
11524
|
-
phase: "reconciling"
|
|
11525
|
-
});
|
|
11526
|
-
await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
|
|
11527
|
-
return {
|
|
11528
|
-
branch,
|
|
11529
|
-
worktreeId: initialized.meta.worktreeId
|
|
11530
|
-
};
|
|
11463
|
+
for (const target of targets) {
|
|
11464
|
+
const created = await this.createResolvedWorktree({
|
|
11465
|
+
...input,
|
|
11466
|
+
mode,
|
|
11467
|
+
branch: target.branch,
|
|
11468
|
+
agent: target.agent
|
|
11469
|
+
});
|
|
11470
|
+
createdBranches.push(created.branch);
|
|
11471
|
+
}
|
|
11531
11472
|
} catch (error) {
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
throw this.wrapOperationError(new Error(`${toErrorMessage2(error)}; ${cleanupError}`));
|
|
11536
|
-
}
|
|
11473
|
+
const rollbackError = await this.rollbackCreatedWorktrees(createdBranches);
|
|
11474
|
+
if (rollbackError) {
|
|
11475
|
+
throw this.wrapOperationError(new Error(`${toErrorMessage2(error)}; ${rollbackError}`));
|
|
11537
11476
|
}
|
|
11538
11477
|
throw this.wrapOperationError(error);
|
|
11539
|
-
} finally {
|
|
11540
|
-
await this.finishCreateProgress(branch);
|
|
11541
11478
|
}
|
|
11479
|
+
return {
|
|
11480
|
+
primaryBranch: createdBranches[0],
|
|
11481
|
+
branches: createdBranches
|
|
11482
|
+
};
|
|
11483
|
+
}
|
|
11484
|
+
async createWorktree(input) {
|
|
11485
|
+
const mode = input.mode ?? "new";
|
|
11486
|
+
const branch = await this.resolveBranch(input.branch, input.prompt, mode);
|
|
11487
|
+
const agent = this.resolveAgent(input.agent);
|
|
11488
|
+
return await this.createResolvedWorktree({
|
|
11489
|
+
...input,
|
|
11490
|
+
mode,
|
|
11491
|
+
branch,
|
|
11492
|
+
agent
|
|
11493
|
+
});
|
|
11542
11494
|
}
|
|
11543
11495
|
async openWorktree(branch) {
|
|
11544
11496
|
try {
|
|
@@ -11939,6 +11891,123 @@ class LifecycleService {
|
|
|
11939
11891
|
async finishCreateProgress(branch) {
|
|
11940
11892
|
await this.deps.onCreateFinished?.(branch);
|
|
11941
11893
|
}
|
|
11894
|
+
async rollbackCreatedWorktrees(branches) {
|
|
11895
|
+
const cleanupErrors = [];
|
|
11896
|
+
for (const branch of [...branches].reverse()) {
|
|
11897
|
+
try {
|
|
11898
|
+
await this.removeWorktree(branch);
|
|
11899
|
+
} catch (error) {
|
|
11900
|
+
cleanupErrors.push(`rollback failed for ${branch}: ${toErrorMessage2(error)}`);
|
|
11901
|
+
}
|
|
11902
|
+
}
|
|
11903
|
+
return cleanupErrors.length > 0 ? cleanupErrors.join("; ") : null;
|
|
11904
|
+
}
|
|
11905
|
+
async createResolvedWorktree(input) {
|
|
11906
|
+
const requestedBaseBranch = input.baseBranch?.trim();
|
|
11907
|
+
if (requestedBaseBranch && !isValidBranchName(requestedBaseBranch)) {
|
|
11908
|
+
throw new LifecycleError("Invalid base branch name", 400);
|
|
11909
|
+
}
|
|
11910
|
+
if (requestedBaseBranch && input.mode === "existing") {
|
|
11911
|
+
throw new LifecycleError("Base branch is only supported for new worktrees", 400);
|
|
11912
|
+
}
|
|
11913
|
+
if (requestedBaseBranch && requestedBaseBranch === input.branch) {
|
|
11914
|
+
throw new LifecycleError("Base branch must differ from branch name", 400);
|
|
11915
|
+
}
|
|
11916
|
+
const baseBranch = input.mode === "new" ? requestedBaseBranch || this.deps.config.workspace.mainBranch : undefined;
|
|
11917
|
+
const branchAvailability = this.resolveBranchAvailability(input.branch, input.mode);
|
|
11918
|
+
const { profileName, profile } = this.resolveProfile(input.profile);
|
|
11919
|
+
const worktreePath = this.resolveWorktreePath(input.branch);
|
|
11920
|
+
const createProgressBase = {
|
|
11921
|
+
branch: input.branch,
|
|
11922
|
+
...baseBranch ? { baseBranch } : {},
|
|
11923
|
+
path: worktreePath,
|
|
11924
|
+
profile: profileName,
|
|
11925
|
+
agent: input.agent
|
|
11926
|
+
};
|
|
11927
|
+
const deleteBranchOnRollback = input.mode === "new" || branchAvailability.deleteBranchOnRollback;
|
|
11928
|
+
let initialized = null;
|
|
11929
|
+
try {
|
|
11930
|
+
await this.reportCreateProgress({
|
|
11931
|
+
...createProgressBase,
|
|
11932
|
+
phase: "creating_worktree"
|
|
11933
|
+
});
|
|
11934
|
+
await mkdir4(dirname5(worktreePath), { recursive: true });
|
|
11935
|
+
initialized = await createManagedWorktree({
|
|
11936
|
+
repoRoot: this.deps.projectRoot,
|
|
11937
|
+
worktreePath,
|
|
11938
|
+
branch: input.branch,
|
|
11939
|
+
mode: input.mode,
|
|
11940
|
+
...baseBranch ? { baseBranch } : {},
|
|
11941
|
+
...branchAvailability.startPoint ? { startPoint: branchAvailability.startPoint } : {},
|
|
11942
|
+
profile: profileName,
|
|
11943
|
+
agent: input.agent,
|
|
11944
|
+
runtime: profile.runtime,
|
|
11945
|
+
startupEnvValues: await this.buildStartupEnvValues(input.envOverrides),
|
|
11946
|
+
allocatedPorts: await this.allocatePorts(),
|
|
11947
|
+
runtimeEnvExtras: { WEBMUX_WORKTREE_PATH: worktreePath },
|
|
11948
|
+
controlUrl: this.controlUrl(),
|
|
11949
|
+
controlToken: await this.deps.getControlToken(),
|
|
11950
|
+
deleteBranchOnRollback
|
|
11951
|
+
}, {
|
|
11952
|
+
git: this.deps.git
|
|
11953
|
+
});
|
|
11954
|
+
await this.reportCreateProgress({
|
|
11955
|
+
...createProgressBase,
|
|
11956
|
+
phase: "running_post_create_hook"
|
|
11957
|
+
});
|
|
11958
|
+
await this.runLifecycleHook({
|
|
11959
|
+
name: "postCreate",
|
|
11960
|
+
command: this.deps.config.lifecycleHooks.postCreate,
|
|
11961
|
+
meta: initialized.meta,
|
|
11962
|
+
worktreePath
|
|
11963
|
+
});
|
|
11964
|
+
initialized = await this.refreshManagedArtifactsFromMeta({
|
|
11965
|
+
gitDir: initialized.paths.gitDir,
|
|
11966
|
+
meta: initialized.meta,
|
|
11967
|
+
worktreePath
|
|
11968
|
+
});
|
|
11969
|
+
await this.reportCreateProgress({
|
|
11970
|
+
...createProgressBase,
|
|
11971
|
+
phase: "preparing_runtime"
|
|
11972
|
+
});
|
|
11973
|
+
await ensureAgentRuntimeArtifacts({
|
|
11974
|
+
gitDir: initialized.paths.gitDir,
|
|
11975
|
+
worktreePath
|
|
11976
|
+
});
|
|
11977
|
+
await this.reportCreateProgress({
|
|
11978
|
+
...createProgressBase,
|
|
11979
|
+
phase: "starting_session"
|
|
11980
|
+
});
|
|
11981
|
+
await this.materializeRuntimeSession({
|
|
11982
|
+
branch: input.branch,
|
|
11983
|
+
profile,
|
|
11984
|
+
agent: input.agent,
|
|
11985
|
+
initialized,
|
|
11986
|
+
worktreePath,
|
|
11987
|
+
prompt: input.prompt,
|
|
11988
|
+
launchMode: "fresh"
|
|
11989
|
+
});
|
|
11990
|
+
await this.reportCreateProgress({
|
|
11991
|
+
...createProgressBase,
|
|
11992
|
+
phase: "reconciling"
|
|
11993
|
+
});
|
|
11994
|
+
await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
|
|
11995
|
+
return {
|
|
11996
|
+
branch: input.branch,
|
|
11997
|
+
worktreeId: initialized.meta.worktreeId
|
|
11998
|
+
};
|
|
11999
|
+
} catch (error) {
|
|
12000
|
+
if (initialized) {
|
|
12001
|
+
const cleanupError = await this.cleanupFailedCreate(input.branch, worktreePath, profile.runtime, deleteBranchOnRollback);
|
|
12002
|
+
if (cleanupError) {
|
|
12003
|
+
throw this.wrapOperationError(new Error(`${toErrorMessage2(error)}; ${cleanupError}`));
|
|
12004
|
+
}
|
|
12005
|
+
}
|
|
12006
|
+
throw this.wrapOperationError(error);
|
|
12007
|
+
} finally {
|
|
12008
|
+
await this.finishCreateProgress(input.branch);
|
|
12009
|
+
}
|
|
12010
|
+
}
|
|
11942
12011
|
wrapOperationError(error) {
|
|
11943
12012
|
if (error instanceof LifecycleError) {
|
|
11944
12013
|
return error;
|
|
@@ -12461,6 +12530,7 @@ function createWebmuxRuntime(options = {}) {
|
|
|
12461
12530
|
autoName,
|
|
12462
12531
|
onCreateProgress: (progress) => {
|
|
12463
12532
|
worktreeCreationTracker.set(progress);
|
|
12533
|
+
options.onCreateProgress?.(progress);
|
|
12464
12534
|
},
|
|
12465
12535
|
onCreateFinished: (branch) => {
|
|
12466
12536
|
worktreeCreationTracker.clear(branch);
|
|
@@ -12811,8 +12881,14 @@ async function runWorktreeCommand(context, deps2 = {}) {
|
|
|
12811
12881
|
}
|
|
12812
12882
|
const runtime2 = createRuntime({
|
|
12813
12883
|
projectDir: context.projectDir,
|
|
12814
|
-
port: context.port
|
|
12884
|
+
port: context.port,
|
|
12885
|
+
onCreateProgress: (progress) => {
|
|
12886
|
+
stdout(PHASE_LABELS[progress.phase] ?? progress.phase);
|
|
12887
|
+
}
|
|
12815
12888
|
});
|
|
12889
|
+
if (!parsed.input.branch && parsed.input.prompt && runtime2.config.autoName) {
|
|
12890
|
+
stdout("Generating branch name...");
|
|
12891
|
+
}
|
|
12816
12892
|
const result = await runtime2.lifecycleService.createWorktree(parsed.input);
|
|
12817
12893
|
stdout(`Created worktree ${result.branch}`);
|
|
12818
12894
|
if (!parsed.detach) {
|
|
@@ -12925,13 +13001,20 @@ async function runWorktreeCommand(context, deps2 = {}) {
|
|
|
12925
13001
|
return 1;
|
|
12926
13002
|
}
|
|
12927
13003
|
}
|
|
12928
|
-
var CommandUsageError;
|
|
13004
|
+
var PHASE_LABELS, CommandUsageError;
|
|
12929
13005
|
var init_worktree_commands = __esm(() => {
|
|
12930
13006
|
init_dist2();
|
|
12931
13007
|
init_fs();
|
|
12932
13008
|
init_tmux();
|
|
12933
13009
|
init_policies();
|
|
12934
13010
|
init_runtime();
|
|
13011
|
+
PHASE_LABELS = {
|
|
13012
|
+
creating_worktree: "Creating worktree",
|
|
13013
|
+
running_post_create_hook: "Running post-create hook",
|
|
13014
|
+
preparing_runtime: "Preparing runtime",
|
|
13015
|
+
starting_session: "Starting session",
|
|
13016
|
+
reconciling: "Reconciling"
|
|
13017
|
+
};
|
|
12935
13018
|
CommandUsageError = class CommandUsageError extends Error {
|
|
12936
13019
|
};
|
|
12937
13020
|
});
|
|
@@ -12943,7 +13026,7 @@ import { fileURLToPath } from "url";
|
|
|
12943
13026
|
// package.json
|
|
12944
13027
|
var package_default = {
|
|
12945
13028
|
name: "webmux",
|
|
12946
|
-
version: "0.
|
|
13029
|
+
version: "0.24.0",
|
|
12947
13030
|
description: "Web dashboard for workmux \u2014 browser UI with embedded terminals, PR monitoring, and CI integration",
|
|
12948
13031
|
type: "module",
|
|
12949
13032
|
repository: {
|