@prover-coder-ai/docker-git 1.0.41 → 1.0.42

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.
@@ -269,6 +269,8 @@ var defaultTemplateConfig = {
269
269
  codexAuthPath: "./.docker-git/.orch/auth/codex",
270
270
  codexSharedAuthPath: "./.docker-git/.orch/auth/codex",
271
271
  codexHome: "/home/dev/.codex",
272
+ cpuLimit: "30%",
273
+ ramLimit: "30%",
272
274
  dockerNetworkMode: defaultDockerNetworkMode,
273
275
  dockerSharedNetworkName: defaultDockerSharedNetworkName,
274
276
  enableMcpPlaywright: false,
@@ -1336,6 +1338,8 @@ var TemplateConfigSchema = Schema.Struct({
1336
1338
  codexAuthPath: Schema.String,
1337
1339
  codexSharedAuthPath: Schema.optionalWith(Schema.String, { default: () => defaultTemplateConfig.codexSharedAuthPath }),
1338
1340
  codexHome: Schema.String,
1341
+ cpuLimit: Schema.optionalWith(Schema.String, { default: () => defaultTemplateConfig.cpuLimit }),
1342
+ ramLimit: Schema.optionalWith(Schema.String, { default: () => defaultTemplateConfig.ramLimit }),
1339
1343
  dockerNetworkMode: Schema.optionalWith(Schema.Literal("shared", "project"), { default: () => defaultTemplateConfig.dockerNetworkMode }),
1340
1344
  dockerSharedNetworkName: Schema.optionalWith(Schema.String, { default: () => defaultTemplateConfig.dockerSharedNetworkName }),
1341
1345
  enableMcpPlaywright: Schema.optionalWith(Schema.Boolean, { default: () => defaultTemplateConfig.enableMcpPlaywright }),
@@ -1506,6 +1510,84 @@ var withProjectIndexAndSsh = (run) => pipe(loadProjectIndex(), Effect.flatMap((i
1506
1510
  return yield* _(run(index, yield* _(findSshPrivateKey(yield* _(FileSystem.FileSystem), yield* _(Path.Path), process.cwd()))));
1507
1511
  })));
1508
1512
  //#endregion
1513
+ //#region ../lib/src/core/resource-limits.ts
1514
+ var mebibyte = 1024 ** 2;
1515
+ var minimumResolvedCpuLimit = .25;
1516
+ var minimumResolvedRamLimitMib = 512;
1517
+ var precisionScale = 100;
1518
+ var cpuAbsolutePattern = /^\d+(?:\.\d+)?$/u;
1519
+ var ramAbsolutePattern = /^\d+(?:\.\d+)?(?:b|k|kb|m|mb|g|gb|t|tb)$/iu;
1520
+ var percentPattern = /^\d+(?:\.\d+)?%$/u;
1521
+ var normalizePrecision = (value) => Math.round(value * precisionScale) / precisionScale;
1522
+ var missingLimit = () => void 0;
1523
+ var parsePercent = (candidate) => {
1524
+ if (!percentPattern.test(candidate)) return null;
1525
+ const parsed = Number(candidate.slice(0, -1));
1526
+ if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 100) return null;
1527
+ return normalizePrecision(parsed);
1528
+ };
1529
+ var percentReason = (kind) => kind === "cpu" ? "expected CPU like 30% or 1.5" : "expected RAM like 30%, 512m or 4g";
1530
+ var normalizePercent = (candidate, kind) => {
1531
+ const parsed = parsePercent(candidate);
1532
+ if (parsed === null) return Either.left({
1533
+ _tag: "InvalidOption",
1534
+ option: kind === "cpu" ? "--cpu" : "--ram",
1535
+ reason: percentReason(kind)
1536
+ });
1537
+ return Either.right(`${parsed}%`);
1538
+ };
1539
+ var normalizeCpuLimit = (value, option) => {
1540
+ const candidate = value?.trim().toLowerCase() ?? "";
1541
+ if (candidate.length === 0) return Either.right(missingLimit());
1542
+ if (candidate.endsWith("%")) return normalizePercent(candidate, "cpu");
1543
+ if (!cpuAbsolutePattern.test(candidate)) return Either.left({
1544
+ _tag: "InvalidOption",
1545
+ option,
1546
+ reason: "expected CPU like 30% or 1.5"
1547
+ });
1548
+ const parsed = Number(candidate);
1549
+ if (!Number.isFinite(parsed) || parsed <= 0) return Either.left({
1550
+ _tag: "InvalidOption",
1551
+ option,
1552
+ reason: "must be greater than 0"
1553
+ });
1554
+ return Either.right(String(normalizePrecision(parsed)));
1555
+ };
1556
+ var normalizeRamLimit = (value, option) => {
1557
+ const candidate = value?.trim().toLowerCase() ?? "";
1558
+ if (candidate.length === 0) return Either.right(missingLimit());
1559
+ if (candidate.endsWith("%")) return normalizePercent(candidate, "ram");
1560
+ if (!ramAbsolutePattern.test(candidate)) return Either.left({
1561
+ _tag: "InvalidOption",
1562
+ option,
1563
+ reason: "expected RAM like 30%, 512m or 4g"
1564
+ });
1565
+ return Either.right(candidate);
1566
+ };
1567
+ var withDefaultResourceLimitIntent = (template) => ({
1568
+ ...template,
1569
+ cpuLimit: template.cpuLimit ?? "30%",
1570
+ ramLimit: template.ramLimit ?? "30%"
1571
+ });
1572
+ var resolvePercentCpuLimit = (percent, cpuCount) => Math.max(minimumResolvedCpuLimit, normalizePrecision(Math.max(1, cpuCount) * percent / 100));
1573
+ var resolvePercentRamLimit = (percent, totalMemoryBytes) => {
1574
+ const totalMib = Math.max(minimumResolvedRamLimitMib, Math.floor(totalMemoryBytes / mebibyte));
1575
+ return `${Math.max(minimumResolvedRamLimitMib, Math.floor(totalMib * percent / 100))}m`;
1576
+ };
1577
+ var resolveComposeResourceLimits = (template, hostResources) => {
1578
+ const cpuLimitIntent = template.cpuLimit ?? "30%";
1579
+ const ramLimitIntent = template.ramLimit ?? "30%";
1580
+ const cpuPercent = parsePercent(cpuLimitIntent);
1581
+ const ramPercent = parsePercent(ramLimitIntent);
1582
+ return {
1583
+ cpuLimit: cpuPercent === null ? Number(cpuLimitIntent) : resolvePercentCpuLimit(cpuPercent, hostResources.cpuCount),
1584
+ ramLimit: ramPercent === null ? ramLimitIntent : resolvePercentRamLimit(ramPercent, hostResources.totalMemoryBytes)
1585
+ };
1586
+ };
1587
+ //#endregion
1588
+ //#region ../lib/src/usecases/resource-limits.ts
1589
+ var resolveTemplateResourceLimits = (template) => Effect.succeed(withDefaultResourceLimitIntent(template));
1590
+ //#endregion
1509
1591
  //#region ../lib/src/usecases/state-repo/env.ts
1510
1592
  var isTruthyEnv = (value) => {
1511
1593
  const normalized = value.trim().toLowerCase();
@@ -1543,7 +1625,7 @@ var gitExitCode = (cwd, args, env = gitBaseEnv$1) => runCommandExitCode({
1543
1625
  args,
1544
1626
  env
1545
1627
  });
1546
- var gitCapture = (cwd, args, env = gitBaseEnv$1) => runCommandCapture({
1628
+ var gitCapture$1 = (cwd, args, env = gitBaseEnv$1) => runCommandCapture({
1547
1629
  cwd,
1548
1630
  command: "git",
1549
1631
  args,
@@ -3736,7 +3818,8 @@ var renderAgentModeEnv = (agentMode) => agentMode !== void 0 && agentMode.length
3736
3818
  var renderAgentAutoEnv = (agentAuto) => agentAuto === true ? ` AGENT_AUTO: "1"\n` : "";
3737
3819
  var renderProjectsRootHostMount = (projectsRoot) => `\${DOCKER_GIT_PROJECTS_ROOT_HOST:-${projectsRoot}}`;
3738
3820
  var renderSharedCodexHostMount = (projectsRoot) => `\${DOCKER_GIT_PROJECTS_ROOT_HOST:-${projectsRoot}}/.orch/auth/codex`;
3739
- var buildPlaywrightFragments = (config, networkName) => {
3821
+ var renderResourceLimits = (resourceLimits) => resourceLimits === void 0 ? "" : ` cpus: ${resourceLimits.cpuLimit}\n mem_limit: "${resourceLimits.ramLimit}"\n memswap_limit: "${resourceLimits.ramLimit}"\n`;
3822
+ var buildPlaywrightFragments = (config, networkName, resourceLimits) => {
3740
3823
  if (!config.enableMcpPlaywright) return {
3741
3824
  maybeDependsOn: "",
3742
3825
  maybePlaywrightEnv: "",
@@ -3751,11 +3834,11 @@ var buildPlaywrightFragments = (config, networkName) => {
3751
3834
  return {
3752
3835
  maybeDependsOn: ` depends_on:\n - ${browserServiceName}\n`,
3753
3836
  maybePlaywrightEnv: ` MCP_PLAYWRIGHT_ENABLE: "1"\n MCP_PLAYWRIGHT_CDP_ENDPOINT: "${browserCdpEndpoint}"\n`,
3754
- maybeBrowserService: `\n ${browserServiceName}:\n build:\n context: .\n dockerfile: ${browserDockerfile}\n container_name: ${browserContainerName}\n restart: unless-stopped\n environment:\n VNC_NOPW: "1"\n shm_size: "2gb"\n expose:\n - "9223"\n volumes:\n - ${browserVolumeName}:/data\n networks:\n - ${networkName}\n`,
3837
+ maybeBrowserService: `\n ${browserServiceName}:\n build:\n context: .\n dockerfile: ${browserDockerfile}\n container_name: ${browserContainerName}\n restart: unless-stopped\n${renderResourceLimits(resourceLimits)} environment:\n VNC_NOPW: "1"\n shm_size: "2gb"\n expose:\n - "9223"\n volumes:\n - ${browserVolumeName}:/data\n networks:\n - ${networkName}\n`,
3755
3838
  maybeBrowserVolume: ` ${browserVolumeName}:\n`
3756
3839
  };
3757
3840
  };
3758
- var buildComposeFragments = (config) => {
3841
+ var buildComposeFragments = (config, resourceLimits) => {
3759
3842
  const networkMode = config.dockerNetworkMode;
3760
3843
  const networkName = resolveComposeNetworkName(config);
3761
3844
  const forkRepoUrl = config.forkRepoUrl ?? "";
@@ -3767,7 +3850,7 @@ var buildComposeFragments = (config) => {
3767
3850
  const maybeClaudeAuthLabelEnv = renderClaudeAuthLabelEnv(claudeAuthLabel);
3768
3851
  const maybeAgentModeEnv = renderAgentModeEnv(config.agentMode);
3769
3852
  const maybeAgentAutoEnv = renderAgentAutoEnv(config.agentAuto);
3770
- const playwright = buildPlaywrightFragments(config, networkName);
3853
+ const playwright = buildPlaywrightFragments(config, networkName, resourceLimits);
3771
3854
  return {
3772
3855
  networkMode,
3773
3856
  networkName,
@@ -3783,7 +3866,7 @@ var buildComposeFragments = (config) => {
3783
3866
  forkRepoUrl
3784
3867
  };
3785
3868
  };
3786
- var renderComposeServices = (config, fragments) => `services:
3869
+ var renderComposeServices = (config, fragments, resourceLimits) => `services:
3787
3870
  ${config.serviceName}:
3788
3871
  build: .
3789
3872
  container_name: ${config.containerName}
@@ -3802,7 +3885,7 @@ ${fragments.maybePlaywrightEnv}${fragments.maybeDependsOn} env_file:
3802
3885
  - ${config.envProjectPath}
3803
3886
  ports:
3804
3887
  - "127.0.0.1:${config.sshPort}:22"
3805
- volumes:
3888
+ ${renderResourceLimits(resourceLimits)} volumes:
3806
3889
  - ${config.volumeName}:/home/${config.sshUser}
3807
3890
  - ${renderProjectsRootHostMount(config.dockerGitPath)}:/home/${config.sshUser}/.docker-git
3808
3891
  - ${config.authorizedKeysPath}:/authorized_keys:ro
@@ -3820,10 +3903,10 @@ var renderComposeNetworks = (networkMode, networkName) => networkMode === "share
3820
3903
  var renderComposeVolumes = (config, maybeBrowserVolume) => `volumes:
3821
3904
  ${config.volumeName}:
3822
3905
  ${maybeBrowserVolume}`;
3823
- var renderDockerCompose = (config) => {
3824
- const fragments = buildComposeFragments(config);
3906
+ var renderDockerCompose = (config, resourceLimits) => {
3907
+ const fragments = buildComposeFragments(config, resourceLimits);
3825
3908
  return [
3826
- renderComposeServices(config, fragments),
3909
+ renderComposeServices(config, fragments, resourceLimits),
3827
3910
  renderComposeNetworks(fragments.networkMode, fragments.networkName),
3828
3911
  renderComposeVolumes(config, fragments.maybeBrowserVolume)
3829
3912
  ].join("\n\n");
@@ -4097,7 +4180,7 @@ var renderConfigJson = (config) => `${JSON.stringify({
4097
4180
  template: config
4098
4181
  }, null, 2)}
4099
4182
  `;
4100
- var planFiles = (config) => {
4183
+ var planFiles = (config, composeResourceLimits) => {
4101
4184
  const maybePlaywrightFiles = config.enableMcpPlaywright ? [{
4102
4185
  _tag: "File",
4103
4186
  relativePath: "Dockerfile.browser",
@@ -4123,7 +4206,7 @@ var planFiles = (config) => {
4123
4206
  {
4124
4207
  _tag: "File",
4125
4208
  relativePath: "docker-compose.yml",
4126
- contents: renderDockerCompose(config)
4209
+ contents: renderDockerCompose(config, composeResourceLimits)
4127
4210
  },
4128
4211
  {
4129
4212
  _tag: "File",
@@ -4154,6 +4237,20 @@ var planFiles = (config) => {
4154
4237
  //#endregion
4155
4238
  //#region ../lib/src/shell/files.ts
4156
4239
  var ensureParentDir = (path, fs, filePath) => fs.makeDirectory(path.dirname(filePath), { recursive: true });
4240
+ var fallbackHostResources = {
4241
+ cpuCount: 1,
4242
+ totalMemoryBytes: 1024 ** 3
4243
+ };
4244
+ var loadHostResources = () => Effect.tryPromise({
4245
+ try: () => import("node:os").then((os) => ({
4246
+ cpuCount: os.availableParallelism(),
4247
+ totalMemoryBytes: os.totalmem()
4248
+ })),
4249
+ catch: (error) => new Error(String(error))
4250
+ }).pipe(Effect.match({
4251
+ onFailure: () => fallbackHostResources,
4252
+ onSuccess: (value) => value
4253
+ }));
4157
4254
  var isFileSpec = (spec) => spec._tag === "File";
4158
4255
  var resolveSpecPath = (path, baseDir, spec) => path.join(baseDir, spec.relativePath);
4159
4256
  var writeSpec = (path, fs, baseDir, spec) => {
@@ -4181,7 +4278,8 @@ var failOnExistingFiles = (existingFilePaths, skipExistingFiles) => {
4181
4278
  var writeProjectFiles = (outDir, config, force, skipExistingFiles = false) => Effect.gen(function* (_) {
4182
4279
  const { fs, path, resolved: baseDir } = yield* _(resolveBaseDir(outDir));
4183
4280
  yield* _(fs.makeDirectory(baseDir, { recursive: true }));
4184
- const specs = planFiles(config);
4281
+ const normalizedConfig = withDefaultResourceLimitIntent(config);
4282
+ const specs = planFiles(normalizedConfig, resolveComposeResourceLimits(normalizedConfig, yield* _(loadHostResources())));
4185
4283
  const created = [];
4186
4284
  const existingFilePaths = force ? [] : yield* _(collectExistingFilePaths(fs, path, baseDir, specs));
4187
4285
  const existingSet = new Set(existingFilePaths);
@@ -4301,7 +4399,7 @@ var rebaseOntoOriginIfPossible = (root, baseBranch, env) => Effect.gen(function*
4301
4399
  return "conflict";
4302
4400
  });
4303
4401
  var pushToNewBranch = (root, baseBranch, originPushTarget, env) => Effect.gen(function* (_) {
4304
- const headShort = yield* _(gitCapture(root, [
4402
+ const headShort = yield* _(gitCapture$1(root, [
4305
4403
  "rev-parse",
4306
4404
  "--short",
4307
4405
  "HEAD"
@@ -4316,7 +4414,7 @@ var pushToNewBranch = (root, baseBranch, originPushTarget, env) => Effect.gen(fu
4316
4414
  return branch;
4317
4415
  });
4318
4416
  var resolveBaseBranch = (value) => value === "HEAD" ? "main" : value;
4319
- var getCurrentBranch = (root, env) => gitCapture(root, [
4417
+ var getCurrentBranch = (root, env) => gitCapture$1(root, [
4320
4418
  "rev-parse",
4321
4419
  "--abbrev-ref",
4322
4420
  "HEAD"
@@ -4391,7 +4489,7 @@ var stateSync = (message) => Effect.gen(function* (_) {
4391
4489
  exitCode: originUrlExit
4392
4490
  })));
4393
4491
  }
4394
- const originUrl = yield* _(gitCapture(root, [
4492
+ const originUrl = yield* _(gitCapture$1(root, [
4395
4493
  "remote",
4396
4494
  "get-url",
4397
4495
  "origin"
@@ -4495,7 +4593,7 @@ var stateInit = (input) => Effect.gen(function* (_) {
4495
4593
  yield* _(Effect.log(`Remote: ${input.repoUrl}`));
4496
4594
  }).pipe(Effect.asVoid);
4497
4595
  var stateStatus = Effect.gen(function* (_) {
4498
- const output = yield* _(gitCapture(resolveStateRoot(yield* _(Path.Path), process.cwd()), [
4596
+ const output = yield* _(gitCapture$1(resolveStateRoot(yield* _(Path.Path), process.cwd()), [
4499
4597
  "status",
4500
4598
  "-sb",
4501
4599
  "--porcelain=v1"
@@ -4514,7 +4612,7 @@ var statePull = Effect.gen(function* (_) {
4514
4612
  yield* _(git(root, ["pull", "--rebase"], gitBaseEnv$1));
4515
4613
  return;
4516
4614
  }
4517
- const originUrl = yield* _(gitCapture(root, [
4615
+ const originUrl = yield* _(gitCapture$1(root, [
4518
4616
  "remote",
4519
4617
  "get-url",
4520
4618
  "origin"
@@ -4539,13 +4637,13 @@ var statePush = Effect.gen(function* (_) {
4539
4637
  ], gitBaseEnv$1));
4540
4638
  return;
4541
4639
  }
4542
- const originUrl = yield* _(gitCapture(root, [
4640
+ const originUrl = yield* _(gitCapture$1(root, [
4543
4641
  "remote",
4544
4642
  "get-url",
4545
4643
  "origin"
4546
4644
  ], gitBaseEnv$1).pipe(Effect.map((value) => value.trim())));
4547
4645
  const token = yield* _(resolveGithubToken(fs, path, root));
4548
- yield* _(token && token.length > 0 && isGithubHttpsRemote(originUrl) ? withGithubAskpassEnv(token, (env) => pipe(gitCapture(root, [
4646
+ yield* _(token && token.length > 0 && isGithubHttpsRemote(originUrl) ? withGithubAskpassEnv(token, (env) => pipe(gitCapture$1(root, [
4549
4647
  "rev-parse",
4550
4648
  "--abbrev-ref",
4551
4649
  "HEAD"
@@ -5105,18 +5203,18 @@ var ensureAvailableSshPort = (projectDir, config) => Effect.gen(function* (_) {
5105
5203
  });
5106
5204
  var runDockerComposeUpWithPortCheck = (projectDir) => Effect.gen(function* (_) {
5107
5205
  const config = yield* _(readProjectConfig(projectDir));
5108
- const updated = (yield* _(runDockerComposePsFormatted(projectDir).pipe(Effect.map((raw) => parseComposePsOutput(raw)), Effect.map((rows) => rows.length > 0)))) ? config.template : yield* _(ensureAvailableSshPort(projectDir, config));
5109
- yield* _(syncManagedProjectFiles(projectDir, updated));
5110
- yield* _(ensureComposeNetworkReady(projectDir, updated));
5206
+ const resolvedTemplate = yield* _(resolveTemplateResourceLimits((yield* _(runDockerComposePsFormatted(projectDir).pipe(Effect.map((raw) => parseComposePsOutput(raw)), Effect.map((rows) => rows.length > 0)))) ? config.template : yield* _(ensureAvailableSshPort(projectDir, config))));
5207
+ yield* _(syncManagedProjectFiles(projectDir, resolvedTemplate));
5208
+ yield* _(ensureComposeNetworkReady(projectDir, resolvedTemplate));
5111
5209
  yield* _(runDockerComposeUp(projectDir));
5112
- yield* _(ensureClaudeCliReady(projectDir, updated.containerName));
5210
+ yield* _(ensureClaudeCliReady(projectDir, resolvedTemplate.containerName));
5113
5211
  const ensureBridgeAccess = (containerName) => runDockerInspectContainerBridgeIp(projectDir, containerName).pipe(Effect.flatMap((bridgeIp) => bridgeIp.length > 0 ? Effect.void : runDockerNetworkConnectBridge(projectDir, containerName)), Effect.matchEffect({
5114
5212
  onFailure: (error) => Effect.logWarning(`Failed to connect ${containerName} to bridge network: ${error instanceof Error ? error.message : String(error)}`),
5115
5213
  onSuccess: () => Effect.void
5116
5214
  }));
5117
- yield* _(ensureBridgeAccess(updated.containerName));
5118
- if (updated.enableMcpPlaywright) yield* _(ensureBridgeAccess(`${updated.containerName}-browser`));
5119
- return updated;
5215
+ yield* _(ensureBridgeAccess(resolvedTemplate.containerName));
5216
+ if (resolvedTemplate.enableMcpPlaywright) yield* _(ensureBridgeAccess(`${resolvedTemplate.containerName}-browser`));
5217
+ return resolvedTemplate;
5120
5218
  });
5121
5219
  //#endregion
5122
5220
  //#region ../lib/src/usecases/projects-ssh.ts
@@ -5408,7 +5506,7 @@ var resolveRootedConfig = (command, ctx) => ({
5408
5506
  codexAuthPath: ctx.resolveRootPath(command.config.codexAuthPath),
5409
5507
  codexSharedAuthPath: ctx.resolveRootPath(command.config.codexSharedAuthPath)
5410
5508
  });
5411
- var resolveCreateConfig = (command, ctx, resolvedOutDir) => resolveSshPort(resolveRootedConfig(command, ctx), resolvedOutDir).pipe(Effect.flatMap((config) => applyGithubForkConfig(config)));
5509
+ var resolveCreateConfig = (command, ctx, resolvedOutDir) => resolveSshPort(resolveRootedConfig(command, ctx), resolvedOutDir).pipe(Effect.flatMap((config) => applyGithubForkConfig(config)), Effect.flatMap((config) => resolveTemplateResourceLimits(config)));
5412
5510
  var logCreatedProject = (resolvedOutDir, createdFiles) => Effect.gen(function* (_) {
5413
5511
  yield* _(Effect.log(`Created docker-git project in ${resolvedOutDir}`));
5414
5512
  for (const file of createdFiles) yield* _(Effect.log(` - ${file}`));
@@ -5524,7 +5622,7 @@ var normalizeAuthLabel = (value) => {
5524
5622
  };
5525
5623
  //#endregion
5526
5624
  //#region ../lib/src/usecases/apply-overrides.ts
5527
- var hasApplyOverrides = (command) => command.gitTokenLabel !== void 0 || command.codexTokenLabel !== void 0 || command.claudeTokenLabel !== void 0 || command.enableMcpPlaywright !== void 0;
5625
+ var hasApplyOverrides = (command) => command.gitTokenLabel !== void 0 || command.codexTokenLabel !== void 0 || command.claudeTokenLabel !== void 0 || command.cpuLimit !== void 0 || command.ramLimit !== void 0 || command.enableMcpPlaywright !== void 0;
5528
5626
  var applyTemplateOverrides = (template, command) => {
5529
5627
  if (command === void 0) return template;
5530
5628
  let nextTemplate = template;
@@ -5540,6 +5638,14 @@ var applyTemplateOverrides = (template, command) => {
5540
5638
  ...nextTemplate,
5541
5639
  claudeAuthLabel: normalizeAuthLabel(command.claudeTokenLabel)
5542
5640
  };
5641
+ if (command.cpuLimit !== void 0) nextTemplate = {
5642
+ ...nextTemplate,
5643
+ cpuLimit: command.cpuLimit
5644
+ };
5645
+ if (command.ramLimit !== void 0) nextTemplate = {
5646
+ ...nextTemplate,
5647
+ ramLimit: command.ramLimit
5648
+ };
5543
5649
  if (command.enableMcpPlaywright !== void 0) nextTemplate = {
5544
5650
  ...nextTemplate,
5545
5651
  enableMcpPlaywright: command.enableMcpPlaywright
@@ -5547,22 +5653,12 @@ var applyTemplateOverrides = (template, command) => {
5547
5653
  return nextTemplate;
5548
5654
  };
5549
5655
  //#endregion
5550
- //#region ../lib/src/usecases/apply.ts
5551
- var applyProjectFiles = (projectDir, command) => Effect.gen(function* (_) {
5552
- yield* _(Effect.log(`Applying docker-git config files in ${projectDir}...`));
5553
- const resolvedTemplate = applyTemplateOverrides((yield* _(readProjectConfig(projectDir))).template, command);
5554
- yield* _(writeProjectFiles(projectDir, resolvedTemplate, true));
5555
- yield* _(ensureCodexConfigFile(projectDir, resolvedTemplate.codexAuthPath));
5556
- yield* _(ensureClaudeAuthSeedFromHome(defaultProjectsRoot(projectDir), ".orch/auth/claude"));
5557
- return resolvedTemplate;
5558
- });
5656
+ //#region ../lib/src/usecases/apply-project-discovery.ts
5559
5657
  var gitSuccessExitCode = 0;
5560
- var gitBranchDetached = "HEAD";
5561
- var maxLocalConfigSearchDepth = 6;
5562
5658
  var gitBaseEnv = { GIT_TERMINAL_PROMPT: "0" };
5563
5659
  var emptyConfigPaths = () => [];
5564
5660
  var nullProjectCandidate = () => null;
5565
- var nullString = () => null;
5661
+ var nullString$1 = () => null;
5566
5662
  var normalizeRepoIdentity = (repoUrl) => {
5567
5663
  const github = parseGithubRepoUrl(repoUrl);
5568
5664
  if (github !== null) {
@@ -5630,7 +5726,7 @@ var tryGitCapture = (cwd, args) => {
5630
5726
  _tag: "ApplyGitCaptureError",
5631
5727
  code
5632
5728
  })).pipe(Effect.map((value) => value.trim()), Effect.match({
5633
- onFailure: nullString,
5729
+ onFailure: nullString$1,
5634
5730
  onSuccess: (value) => value
5635
5731
  })) : Effect.succeed(null)
5636
5732
  }));
@@ -5680,6 +5776,20 @@ var collectRemoteIdentities = (repoRoot) => Effect.gen(function* (_) {
5680
5776
  }
5681
5777
  return [...identityMap.values()];
5682
5778
  });
5779
+ var gitCapture = tryGitCapture;
5780
+ //#endregion
5781
+ //#region ../lib/src/usecases/apply.ts
5782
+ var applyProjectFiles = (projectDir, command) => Effect.gen(function* (_) {
5783
+ yield* _(Effect.log(`Applying docker-git config files in ${projectDir}...`));
5784
+ const resolvedTemplate = yield* _(resolveTemplateResourceLimits(applyTemplateOverrides((yield* _(readProjectConfig(projectDir))).template, command)));
5785
+ yield* _(writeProjectFiles(projectDir, resolvedTemplate, true));
5786
+ yield* _(ensureCodexConfigFile(projectDir, resolvedTemplate.codexAuthPath));
5787
+ yield* _(ensureClaudeAuthSeedFromHome(defaultProjectsRoot(projectDir), ".orch/auth/claude"));
5788
+ return resolvedTemplate;
5789
+ });
5790
+ var gitBranchDetached = "HEAD";
5791
+ var maxLocalConfigSearchDepth = 6;
5792
+ var nullString = () => null;
5683
5793
  var resolveFromCurrentTree = () => Effect.gen(function* (_) {
5684
5794
  const { fs, path, resolved } = yield* _(resolveBaseDir("."));
5685
5795
  const configPath = yield* _(findExistingUpwards(fs, path, resolved, "docker-git.json", maxLocalConfigSearchDepth).pipe(Effect.match({
@@ -5695,11 +5805,11 @@ var normalizeBranch = (branch) => {
5695
5805
  };
5696
5806
  var resolveFromCurrentRepository = () => Effect.gen(function* (_) {
5697
5807
  const cwd = process.cwd();
5698
- const repoRoot = yield* _(tryGitCapture(cwd, ["rev-parse", "--show-toplevel"]));
5808
+ const repoRoot = yield* _(gitCapture(cwd, ["rev-parse", "--show-toplevel"]));
5699
5809
  if (repoRoot === null) return null;
5700
5810
  const remoteIdentities = yield* _(collectRemoteIdentities(repoRoot));
5701
5811
  if (remoteIdentities.length === 0) return null;
5702
- const branch = normalizeBranch(yield* _(tryGitCapture(repoRoot, [
5812
+ const branch = normalizeBranch(yield* _(gitCapture(repoRoot, [
5703
5813
  "rev-parse",
5704
5814
  "--abbrev-ref",
5705
5815
  "HEAD"
@@ -6951,6 +7061,22 @@ var valueOptionSpecByFlag = new Map([
6951
7061
  flag: "--codex-home",
6952
7062
  key: "codexHome"
6953
7063
  },
7064
+ {
7065
+ flag: "--cpu",
7066
+ key: "cpuLimit"
7067
+ },
7068
+ {
7069
+ flag: "--cpus",
7070
+ key: "cpuLimit"
7071
+ },
7072
+ {
7073
+ flag: "--ram",
7074
+ key: "ramLimit"
7075
+ },
7076
+ {
7077
+ flag: "--memory",
7078
+ key: "ramLimit"
7079
+ },
6954
7080
  {
6955
7081
  flag: "--network-mode",
6956
7082
  key: "dockerNetworkMode"
@@ -7127,6 +7253,14 @@ var valueFlagUpdaters = {
7127
7253
  ...raw,
7128
7254
  codexHome: value
7129
7255
  }),
7256
+ cpuLimit: (raw, value) => ({
7257
+ ...raw,
7258
+ cpuLimit: value
7259
+ }),
7260
+ ramLimit: (raw, value) => ({
7261
+ ...raw,
7262
+ ramLimit: value
7263
+ }),
7130
7264
  dockerNetworkMode: (raw, value) => ({
7131
7265
  ...raw,
7132
7266
  dockerNetworkMode: value
@@ -7304,15 +7438,22 @@ var parseProjectDirWithOptions = (args, defaultProjectDir = ".") => Either.gen(f
7304
7438
  var parseProjectDirArgs = (args, defaultProjectDir = ".") => Either.map(parseProjectDirWithOptions(args, defaultProjectDir), ({ projectDir }) => ({ projectDir }));
7305
7439
  //#endregion
7306
7440
  //#region src/docker-git/cli/parser-apply.ts
7307
- var parseApply = (args) => Either.map(parseProjectDirWithOptions(args), ({ projectDir, raw }) => ({
7308
- _tag: "Apply",
7309
- projectDir,
7310
- runUp: raw.up ?? true,
7311
- gitTokenLabel: raw.gitTokenLabel,
7312
- codexTokenLabel: raw.codexTokenLabel,
7313
- claudeTokenLabel: raw.claudeTokenLabel,
7314
- enableMcpPlaywright: raw.enableMcpPlaywright
7315
- }));
7441
+ var parseApply = (args) => Either.gen(function* (_) {
7442
+ const { projectDir, raw } = yield* _(parseProjectDirWithOptions(args));
7443
+ const cpuLimit = yield* _(normalizeCpuLimit(raw.cpuLimit, "--cpu"));
7444
+ const ramLimit = yield* _(normalizeRamLimit(raw.ramLimit, "--ram"));
7445
+ return {
7446
+ _tag: "Apply",
7447
+ projectDir,
7448
+ runUp: raw.up ?? true,
7449
+ gitTokenLabel: raw.gitTokenLabel,
7450
+ codexTokenLabel: raw.codexTokenLabel,
7451
+ claudeTokenLabel: raw.claudeTokenLabel,
7452
+ cpuLimit,
7453
+ ramLimit,
7454
+ enableMcpPlaywright: raw.enableMcpPlaywright
7455
+ };
7456
+ });
7316
7457
  //#endregion
7317
7458
  //#region src/docker-git/cli/parser-attach.ts
7318
7459
  var parseAttach = (args) => {
@@ -7419,7 +7560,7 @@ var resolveAutoAgentFlags = (raw) => {
7419
7560
  });
7420
7561
  };
7421
7562
  //#endregion
7422
- //#region ../lib/src/core/command-builders.ts
7563
+ //#region ../lib/src/core/command-builders-shared.ts
7423
7564
  var parsePort = (value) => {
7424
7565
  const parsed = Number(value);
7425
7566
  if (!Number.isInteger(parsed)) return Either.left({
@@ -7434,6 +7575,7 @@ var parsePort = (value) => {
7434
7575
  });
7435
7576
  return Either.right(parsed);
7436
7577
  };
7578
+ var parseSshPort = (value) => parsePort(value);
7437
7579
  var parseDockerNetworkMode = (value) => {
7438
7580
  const candidate = value?.trim() ?? defaultTemplateConfig.dockerNetworkMode;
7439
7581
  if (isDockerNetworkMode(candidate)) return Either.right(candidate);
@@ -7451,6 +7593,8 @@ var nonEmpty = (option, value, fallback) => {
7451
7593
  });
7452
7594
  return Either.right(candidate);
7453
7595
  };
7596
+ //#endregion
7597
+ //#region ../lib/src/core/command-builders.ts
7454
7598
  var normalizeSecretsRoot = (value) => trimRightChar(value, "/");
7455
7599
  var resolveRepoBasics = (raw) => Either.gen(function* (_) {
7456
7600
  const resolvedRepo = resolveRepoInput(raw.repoUrl?.trim() ?? "");
@@ -7470,7 +7614,7 @@ var resolveRepoBasics = (raw) => Either.gen(function* (_) {
7470
7614
  repoRef,
7471
7615
  targetDir: expandContainerHome(sshUser, yield* _(nonEmpty("--target-dir", raw.targetDir, defaultTemplateConfig.targetDir))),
7472
7616
  sshUser,
7473
- sshPort: yield* _(parsePort(raw.sshPort ?? String(defaultTemplateConfig.sshPort)))
7617
+ sshPort: yield* _(parseSshPort(raw.sshPort ?? String(defaultTemplateConfig.sshPort)))
7474
7618
  };
7475
7619
  });
7476
7620
  var resolveNames = (raw, projectSlug) => Either.gen(function* (_) {
@@ -7525,7 +7669,7 @@ var resolveCreateBehavior = (raw) => ({
7525
7669
  forceEnv: raw.forceEnv ?? false,
7526
7670
  enableMcpPlaywright: raw.enableMcpPlaywright ?? false
7527
7671
  });
7528
- var buildTemplateConfig = ({ agentAuto, agentMode, claudeAuthLabel, codexAuthLabel, dockerNetworkMode, dockerSharedNetworkName, enableMcpPlaywright, gitTokenLabel, names, paths, repo }) => ({
7672
+ var buildTemplateConfig = ({ agentAuto, agentMode, claudeAuthLabel, codexAuthLabel, cpuLimit, dockerNetworkMode, dockerSharedNetworkName, enableMcpPlaywright, gitTokenLabel, names, paths, ramLimit, repo }) => ({
7529
7673
  containerName: names.containerName,
7530
7674
  serviceName: names.serviceName,
7531
7675
  sshUser: repo.sshUser,
@@ -7544,6 +7688,8 @@ var buildTemplateConfig = ({ agentAuto, agentMode, claudeAuthLabel, codexAuthLab
7544
7688
  codexAuthPath: paths.codexAuthPath,
7545
7689
  codexSharedAuthPath: paths.codexSharedAuthPath,
7546
7690
  codexHome: paths.codexHome,
7691
+ cpuLimit,
7692
+ ramLimit,
7547
7693
  dockerNetworkMode,
7548
7694
  dockerSharedNetworkName,
7549
7695
  enableMcpPlaywright,
@@ -7559,6 +7705,8 @@ var buildCreateCommand = (raw) => Either.gen(function* (_) {
7559
7705
  const gitTokenLabel = normalizeGitTokenLabel(raw.gitTokenLabel);
7560
7706
  const codexAuthLabel = normalizeAuthLabel(raw.codexTokenLabel);
7561
7707
  const claudeAuthLabel = normalizeAuthLabel(raw.claudeTokenLabel);
7708
+ const cpuLimit = yield* _(normalizeCpuLimit(raw.cpuLimit ?? "30%", "--cpu"));
7709
+ const ramLimit = yield* _(normalizeRamLimit(raw.ramLimit ?? "30%", "--ram"));
7562
7710
  const dockerNetworkMode = yield* _(parseDockerNetworkMode(raw.dockerNetworkMode));
7563
7711
  const dockerSharedNetworkName = yield* _(nonEmpty("--shared-network", raw.dockerSharedNetworkName, defaultTemplateConfig.dockerSharedNetworkName));
7564
7712
  const { agentAuto, agentMode } = yield* _(resolveAutoAgentFlags(raw));
@@ -7574,6 +7722,8 @@ var buildCreateCommand = (raw) => Either.gen(function* (_) {
7574
7722
  repo,
7575
7723
  names,
7576
7724
  paths,
7725
+ cpuLimit,
7726
+ ramLimit,
7577
7727
  dockerNetworkMode,
7578
7728
  dockerSharedNetworkName,
7579
7729
  gitTokenLabel,
@@ -7856,6 +8006,8 @@ Options:
7856
8006
  --env-project <path> Host path to project env file (default: ./.orch/env/project.env)
7857
8007
  --codex-auth <path> Host path for Codex auth cache (default: <projectsRoot>/.orch/auth/codex)
7858
8008
  --codex-home <path> Container path for Codex auth (default: /home/dev/.codex)
8009
+ --cpu <value> CPU limit: percent or cores (examples: 30%, 1.5; default: 30%)
8010
+ --ram <value> RAM limit: percent or size (examples: 30%, 512m, 4g; default: 30%)
7859
8011
  --network-mode <mode> Compose network mode: shared|project (default: shared)
7860
8012
  --shared-network <name> Shared Docker network name when network-mode=shared (default: docker-git-shared)
7861
8013
  --out-dir <path> Output directory (default: <projectsRoot>/<org>/<repo>[/issue-<id>|/pr-<id>])
@@ -8348,6 +8500,8 @@ var createSteps = [
8348
8500
  "repoUrl",
8349
8501
  "repoRef",
8350
8502
  "outDir",
8503
+ "cpuLimit",
8504
+ "ramLimit",
8351
8505
  "runUp",
8352
8506
  "mcpPlaywright",
8353
8507
  "force"
@@ -8400,15 +8554,38 @@ var menuItems = [
8400
8554
  ];
8401
8555
  //#endregion
8402
8556
  //#region src/docker-git/menu-create.ts
8557
+ var optionalCreateArgs = (input) => [
8558
+ {
8559
+ value: input.repoUrl,
8560
+ args: ["--repo-url", input.repoUrl]
8561
+ },
8562
+ {
8563
+ value: input.repoRef,
8564
+ args: ["--repo-ref", input.repoRef]
8565
+ },
8566
+ {
8567
+ value: input.outDir,
8568
+ args: ["--out-dir", input.outDir]
8569
+ },
8570
+ {
8571
+ value: input.cpuLimit,
8572
+ args: ["--cpu", input.cpuLimit]
8573
+ },
8574
+ {
8575
+ value: input.ramLimit,
8576
+ args: ["--ram", input.ramLimit]
8577
+ }
8578
+ ];
8579
+ var booleanCreateFlags = (input) => [
8580
+ input.runUp ? null : "--no-up",
8581
+ input.enableMcpPlaywright ? "--mcp-playwright" : null,
8582
+ input.force ? "--force" : null,
8583
+ input.forceEnv ? "--force-env" : null
8584
+ ].filter((value) => value !== null);
8403
8585
  var buildCreateArgs = (input) => {
8404
8586
  const args = ["create"];
8405
- if (input.repoUrl.length > 0) args.push("--repo-url", input.repoUrl);
8406
- if (input.repoRef.length > 0) args.push("--repo-ref", input.repoRef);
8407
- if (input.outDir.length > 0) args.push("--out-dir", input.outDir);
8408
- if (!input.runUp) args.push("--no-up");
8409
- if (input.enableMcpPlaywright) args.push("--mcp-playwright");
8410
- if (input.force) args.push("--force");
8411
- if (input.forceEnv) args.push("--force-env");
8587
+ for (const spec of optionalCreateArgs(input)) if (spec.value.length > 0) args.push(spec.args[0], spec.args[1]);
8588
+ for (const flag of booleanCreateFlags(input)) args.push(flag);
8412
8589
  return args;
8413
8590
  };
8414
8591
  var trimLeftSlash = (value) => {
@@ -8441,6 +8618,8 @@ var resolveCreateInputs = (cwd, values) => {
8441
8618
  repoUrl,
8442
8619
  repoRef: values.repoRef ?? resolvedRepoRef ?? "main",
8443
8620
  outDir,
8621
+ cpuLimit: values.cpuLimit ?? "",
8622
+ ramLimit: values.ramLimit ?? "",
8444
8623
  runUp: values.runUp !== false,
8445
8624
  enableMcpPlaywright: values.enableMcpPlaywright === true,
8446
8625
  force: values.force === true,
@@ -8484,6 +8663,12 @@ var applyCreateStep = (input) => Match.value(input.step).pipe(Match.when("repoUr
8484
8663
  }), Match.when("outDir", () => {
8485
8664
  input.nextValues.outDir = input.buffer.length > 0 ? input.buffer : input.currentDefaults.outDir;
8486
8665
  return true;
8666
+ }), Match.when("cpuLimit", () => {
8667
+ input.nextValues.cpuLimit = input.buffer.length > 0 ? input.buffer : input.currentDefaults.cpuLimit;
8668
+ return true;
8669
+ }), Match.when("ramLimit", () => {
8670
+ input.nextValues.ramLimit = input.buffer.length > 0 ? input.buffer : input.currentDefaults.ramLimit;
8671
+ return true;
8487
8672
  }), Match.when("runUp", () => {
8488
8673
  input.nextValues.runUp = parseYesDefault(input.buffer, input.currentDefaults.runUp);
8489
8674
  return true;
@@ -10106,7 +10291,7 @@ var renderProjectAuthPrompt = (view, message) => {
10106
10291
  };
10107
10292
  //#endregion
10108
10293
  //#region src/docker-git/menu-render.ts
10109
- var renderStepLabel = (step, defaults) => Match.value(step).pipe(Match.when("repoUrl", () => "Repo URL (optional for empty workspace)"), Match.when("repoRef", () => `Repo ref [${defaults.repoRef}]`), Match.when("outDir", () => `Output dir [${defaults.outDir}]`), Match.when("runUp", () => `Run docker compose up now? [${defaults.runUp ? "Y" : "n"}]`), Match.when("mcpPlaywright", () => `Enable Playwright MCP (Chromium sidecar)? [${defaults.enableMcpPlaywright ? "y" : "N"}]`), Match.when("force", () => `Force recreate (overwrite files + wipe volumes)? [${defaults.force ? "y" : "N"}]`), Match.exhaustive);
10294
+ var renderStepLabel = (step, defaults) => Match.value(step).pipe(Match.when("repoUrl", () => "Repo URL (optional for empty workspace)"), Match.when("repoRef", () => `Repo ref [${defaults.repoRef}]`), Match.when("outDir", () => `Output dir [${defaults.outDir}]`), Match.when("cpuLimit", () => `CPU limit [${defaults.cpuLimit || "30%"}]`), Match.when("ramLimit", () => `RAM limit [${defaults.ramLimit || "30%"}]`), Match.when("runUp", () => `Run docker compose up now? [${defaults.runUp ? "Y" : "n"}]`), Match.when("mcpPlaywright", () => `Enable Playwright MCP (Chromium sidecar)? [${defaults.enableMcpPlaywright ? "y" : "N"}]`), Match.when("force", () => `Force recreate (overwrite files + wipe volumes)? [${defaults.force ? "y" : "N"}]`), Match.exhaustive);
10110
10295
  var compactElements = (items) => items.filter((item) => item !== null);
10111
10296
  var renderMenuHints = (el) => el(Box, {
10112
10297
  marginTop: 1,