@prover-coder-ai/docker-git 1.0.31 → 1.0.33

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.
@@ -361,6 +361,8 @@ class DockerAccessError extends Data.TaggedError("DockerAccessError") {
361
361
  }
362
362
  class CloneFailedError extends Data.TaggedError("CloneFailedError") {
363
363
  }
364
+ class AgentFailedError extends Data.TaggedError("AgentFailedError") {
365
+ }
364
366
  class PortProbeError extends Data.TaggedError("PortProbeError") {
365
367
  }
366
368
  class CommandFailedError extends Data.TaggedError("CommandFailedError") {
@@ -777,23 +779,26 @@ const renderDockerAccessActionPlan = (issue) => {
777
779
  ];
778
780
  return issue === "PermissionDenied" ? permissionDeniedPlan.join("\n") : daemonUnavailablePlan.join("\n");
779
781
  };
782
+ const renderDockerCommandError = ({ exitCode }) => [
783
+ `docker compose failed with exit code ${exitCode}`,
784
+ "Hint: ensure Docker daemon is running and current user can access /var/run/docker.sock (for example via the docker group).",
785
+ "Hint: if output above contains 'port is already allocated', retry with a free SSH port via --ssh-port <port> (for example --ssh-port 2235), or stop the conflicting project/container.",
786
+ "Hint: if output above contains 'all predefined address pools have been fully subnetted', run `docker network prune -f`, configure Docker `default-address-pools`, or use shared network mode (`--network-mode shared`).",
787
+ "Hint: if output above contains 'lookup auth.docker.io' or 'read udp ... [::1]:53 ... connection refused', fix Docker DNS resolver (set working DNS in host/daemon config) and retry."
788
+ ].join("\n");
789
+ const renderDockerAccessError = ({ details, issue }) => [
790
+ renderDockerAccessHeadline(issue),
791
+ "Hint: ensure Docker daemon is running and current user can access the docker socket.",
792
+ "Hint: if you use rootless Docker, set DOCKER_HOST to your user socket (for example unix:///run/user/$UID/docker.sock).",
793
+ renderDockerAccessActionPlan(issue),
794
+ `Details: ${details}`
795
+ ].join("\n");
780
796
  const renderPrimaryError = (error) => Match.value(error).pipe(
781
797
  Match.when({ _tag: "FileExistsError" }, ({ path }) => `File already exists: ${path} (use --force to overwrite)`),
782
- Match.when({ _tag: "DockerCommandError" }, ({ exitCode }) => [
783
- `docker compose failed with exit code ${exitCode}`,
784
- "Hint: ensure Docker daemon is running and current user can access /var/run/docker.sock (for example via the docker group).",
785
- "Hint: if output above contains 'port is already allocated', retry with a free SSH port via --ssh-port <port> (for example --ssh-port 2235), or stop the conflicting project/container.",
786
- "Hint: if output above contains 'all predefined address pools have been fully subnetted', run `docker network prune -f`, configure Docker `default-address-pools`, or use shared network mode (`--network-mode shared`).",
787
- "Hint: if output above contains 'lookup auth.docker.io' or 'read udp ... [::1]:53 ... connection refused', fix Docker DNS resolver (set working DNS in host/daemon config) and retry."
788
- ].join("\n")),
789
- Match.when({ _tag: "DockerAccessError" }, ({ details, issue }) => [
790
- renderDockerAccessHeadline(issue),
791
- "Hint: ensure Docker daemon is running and current user can access the docker socket.",
792
- "Hint: if you use rootless Docker, set DOCKER_HOST to your user socket (for example unix:///run/user/$UID/docker.sock).",
793
- renderDockerAccessActionPlan(issue),
794
- `Details: ${details}`
795
- ].join("\n")),
798
+ Match.when({ _tag: "DockerCommandError" }, renderDockerCommandError),
799
+ Match.when({ _tag: "DockerAccessError" }, renderDockerAccessError),
796
800
  Match.when({ _tag: "CloneFailedError" }, ({ repoRef, repoUrl, targetDir }) => `Clone failed for ${repoUrl} (${repoRef}) into ${targetDir}`),
801
+ Match.when({ _tag: "AgentFailedError" }, ({ agentMode, targetDir }) => `Agent (${agentMode}) failed in ${targetDir}`),
797
802
  Match.when({ _tag: "PortProbeError" }, ({ message, port }) => `SSH port check failed for ${port}: ${message}`),
798
803
  Match.when(
799
804
  { _tag: "CommandFailedError" },
@@ -2249,6 +2254,8 @@ GITHUB_TOKEN="\${GITHUB_TOKEN:-\${GH_TOKEN:-}}"
2249
2254
  GIT_USER_NAME="\${GIT_USER_NAME:-}"
2250
2255
  GIT_USER_EMAIL="\${GIT_USER_EMAIL:-}"
2251
2256
  CODEX_AUTO_UPDATE="\${CODEX_AUTO_UPDATE:-1}"
2257
+ AGENT_MODE="\${AGENT_MODE:-}"
2258
+ AGENT_AUTO="\${AGENT_AUTO:-}"
2252
2259
  MCP_PLAYWRIGHT_ENABLE="\${MCP_PLAYWRIGHT_ENABLE:-${config.enableMcpPlaywright ? "1" : "0"}}"
2253
2260
  MCP_PLAYWRIGHT_CDP_ENDPOINT="\${MCP_PLAYWRIGHT_CDP_ENDPOINT:-}"
2254
2261
  MCP_PLAYWRIGHT_ISOLATED="\${MCP_PLAYWRIGHT_ISOLATED:-1}"
@@ -2424,6 +2431,7 @@ if [[ "$CLAUDE_AUTO_SYSTEM_PROMPT" == "1" ]]; then
2424
2431
  $CLAUDE_WORKSPACE_CONTEXT
2425
2432
  Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__
2426
2433
  Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе.
2434
+ Для решения задач обязательно используй subagents. Сам агент обязан выполнять финальную проверку, интеграцию и валидацию результата перед ответом пользователю.
2427
2435
  Если ты видишь файлы AGENTS.md или CLAUDE.md внутри проекта, ты обязан их читать и соблюдать инструкции.
2428
2436
  <!-- /docker-git-managed:claude-md -->
2429
2437
  EOF
@@ -2940,6 +2948,7 @@ WORKSPACES_LINE="Доступные workspace пути: __TARGET_DIR__"
2940
2948
  WORKSPACE_INFO_LINE="Контекст workspace: repository"
2941
2949
  FOCUS_LINE="Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__"
2942
2950
  INTERNET_LINE="Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе."
2951
+ SUBAGENTS_LINE="Для решения задач обязательно используй subagents. Сам агент обязан выполнять финальную проверку, интеграцию и валидацию результата перед ответом пользователю."
2943
2952
  if [[ "$REPO_REF" == issue-* ]]; then
2944
2953
  ISSUE_ID="$(printf "%s" "$REPO_REF" | sed -E 's#^issue-##')"
2945
2954
  ISSUE_URL=""
@@ -2971,9 +2980,9 @@ elif [[ "$REPO_REF" == refs/pull/*/head ]]; then
2971
2980
  WORKSPACE_INFO_LINE="Контекст workspace: pull request ($REPO_REF)"
2972
2981
  fi
2973
2982
  fi
2983
+ MANAGED_START="<!-- docker-git:managed:start -->"
2984
+ MANAGED_END="<!-- docker-git:managed:end -->"
2974
2985
  if [[ ! -f "$AGENTS_PATH" ]]; then
2975
- MANAGED_START="<!-- docker-git:managed:start -->"
2976
- MANAGED_END="<!-- docker-git:managed:end -->"
2977
2986
  MANAGED_BLOCK="$(cat <<EOF
2978
2987
  $MANAGED_START
2979
2988
  $PROJECT_LINE
@@ -2981,6 +2990,7 @@ $WORKSPACES_LINE
2981
2990
  $WORKSPACE_INFO_LINE
2982
2991
  $FOCUS_LINE
2983
2992
  $INTERNET_LINE
2993
+ $SUBAGENTS_LINE
2984
2994
  $MANAGED_END
2985
2995
  EOF
2986
2996
  )"
@@ -2992,8 +3002,6 @@ EOF
2992
3002
  chown 1000:1000 "$AGENTS_PATH" || true
2993
3003
  fi
2994
3004
  if [[ -f "$AGENTS_PATH" ]]; then
2995
- MANAGED_START="<!-- docker-git:managed:start -->"
2996
- MANAGED_END="<!-- docker-git:managed:end -->"
2997
3005
  MANAGED_BLOCK="$(cat <<EOF
2998
3006
  $MANAGED_START
2999
3007
  $PROJECT_LINE
@@ -3001,6 +3009,7 @@ $WORKSPACES_LINE
3001
3009
  $WORKSPACE_INFO_LINE
3002
3010
  $FOCUS_LINE
3003
3011
  $INTERNET_LINE
3012
+ $SUBAGENTS_LINE
3004
3013
  $MANAGED_END
3005
3014
  EOF
3006
3015
  )"
@@ -3020,6 +3029,7 @@ EOF
3020
3029
  -e '/^Фокус задачи:/d' \
3021
3030
  -e '/^Issue AGENTS.md:/d' \
3022
3031
  -e '/^Доступ к интернету:/d' \
3032
+ -e '/^Для решения задач обязательно используй subagents[.]/d' \
3023
3033
  "$AGENTS_PATH" > "$TMP_AGENTS_PATH"
3024
3034
  if [[ -s "$TMP_AGENTS_PATH" ]]; then
3025
3035
  printf "\n" >> "$TMP_AGENTS_PATH"
@@ -3036,7 +3046,10 @@ if [[ -f "$LEGACY_AGENTS_PATH" && -f "$AGENTS_PATH" ]]; then
3036
3046
  rm -f "$LEGACY_AGENTS_PATH"
3037
3047
  fi
3038
3048
  fi`;
3039
- const renderEntrypointAgentsNotice = (config) => entrypointAgentsNoticeTemplate.replaceAll("__CODEX_HOME__", config.codexHome).replaceAll("__SSH_USER__", config.sshUser).replaceAll("__TARGET_DIR__", config.targetDir);
3049
+ const renderEntrypointAgentsNotice = (config) => entrypointAgentsNoticeTemplate.replaceAll("__CODEX_HOME__", config.codexHome).replaceAll(
3050
+ "__SSH_USER__",
3051
+ config.sshUser
3052
+ ).replaceAll("__TARGET_DIR__", config.targetDir);
3040
3053
  const renderAuthLabelResolution = () => String.raw`# 2) Ensure GitHub auth vars are available for SSH sessions.
3041
3054
  # Prefer a label-selected token (same selection model as clone/create) when present.
3042
3055
  RESOLVED_AUTH_LABEL=""
@@ -3568,6 +3581,164 @@ EOF
3568
3581
  chown 1000:1000 "$OPENCODE_CONFIG_JSON" || true
3569
3582
  fi`;
3570
3583
  const renderEntrypointOpenCodeConfig = (config) => entrypointOpenCodeTemplate.replaceAll("__SSH_USER__", config.sshUser).replaceAll("__CODEX_HOME__", config.codexHome);
3584
+ const indentBlock = (block, size = 2) => {
3585
+ const prefix = " ".repeat(size);
3586
+ return block.split("\n").map((line) => `${prefix}${line}`).join("\n");
3587
+ };
3588
+ const renderAgentPrompt = () => String.raw`AGENT_PROMPT=""
3589
+ ISSUE_NUM=""
3590
+ if [[ "$REPO_REF" =~ ^issue-([0-9]+)$ ]]; then
3591
+ ISSUE_NUM="${"${"}BASH_REMATCH[1]}"
3592
+ fi
3593
+
3594
+ if [[ "$AGENT_AUTO" == "1" ]]; then
3595
+ if [[ -n "$ISSUE_NUM" ]]; then
3596
+ AGENT_PROMPT="Read GitHub issue #$ISSUE_NUM for this repository (use gh issue view $ISSUE_NUM). Implement the requested changes, commit them, create a PR that closes #$ISSUE_NUM, and push it."
3597
+ else
3598
+ AGENT_PROMPT="Analyze this repository, implement any pending tasks, commit changes, create a PR, and push it."
3599
+ fi
3600
+ fi`;
3601
+ const renderAgentSetup = () => [
3602
+ String.raw`AGENT_DONE_PATH="/run/docker-git/agent.done"
3603
+ AGENT_FAIL_PATH="/run/docker-git/agent.failed"
3604
+ AGENT_PROMPT_FILE="/run/docker-git/agent-prompt.txt"
3605
+ rm -f "$AGENT_DONE_PATH" "$AGENT_FAIL_PATH" "$AGENT_PROMPT_FILE"`,
3606
+ String.raw`# Collect tokens for agent environment (su - dev does not always inherit profile.d)
3607
+ AGENT_ENV_FILE="/run/docker-git/agent-env.sh"
3608
+ {
3609
+ [[ -f /etc/profile.d/gh-token.sh ]] && cat /etc/profile.d/gh-token.sh
3610
+ [[ -f /etc/profile.d/claude-config.sh ]] && cat /etc/profile.d/claude-config.sh
3611
+ } > "$AGENT_ENV_FILE" 2>/dev/null || true
3612
+ chmod 644 "$AGENT_ENV_FILE"`,
3613
+ renderAgentPrompt(),
3614
+ String.raw`AGENT_OK=0
3615
+ if [[ -n "$AGENT_PROMPT" ]]; then
3616
+ printf "%s" "$AGENT_PROMPT" > "$AGENT_PROMPT_FILE"
3617
+ chmod 644 "$AGENT_PROMPT_FILE"
3618
+ fi`
3619
+ ].join("\n\n");
3620
+ const renderAgentPromptCommand = (mode) => mode === "claude" ? String.raw`claude --dangerously-skip-permissions -p \"\$(cat $AGENT_PROMPT_FILE)\"` : String.raw`codex --approval-mode full-auto \"\$(cat $AGENT_PROMPT_FILE)\"`;
3621
+ const renderAgentModeBlock = (config, mode) => {
3622
+ const startMessage = `[agent] starting ${mode}...`;
3623
+ const interactiveMessage = `[agent] ${mode} started in interactive mode (use SSH to connect)`;
3624
+ return String.raw`"${mode}")
3625
+ echo "${startMessage}"
3626
+ if [[ -n "$AGENT_PROMPT" ]]; then
3627
+ if su - ${config.sshUser} \
3628
+ -c ". /run/docker-git/agent-env.sh 2>/dev/null; cd '$TARGET_DIR' && ${renderAgentPromptCommand(mode)}"; then
3629
+ AGENT_OK=1
3630
+ fi
3631
+ else
3632
+ echo "${interactiveMessage}"
3633
+ AGENT_OK=1
3634
+ fi
3635
+ ;;`;
3636
+ };
3637
+ const renderAgentModeCase = (config) => [
3638
+ String.raw`case "$AGENT_MODE" in`,
3639
+ indentBlock(renderAgentModeBlock(config, "claude")),
3640
+ indentBlock(renderAgentModeBlock(config, "codex")),
3641
+ indentBlock(
3642
+ String.raw`*)
3643
+ echo "[agent] unknown agent mode: $AGENT_MODE"
3644
+ ;;`
3645
+ ),
3646
+ "esac"
3647
+ ].join("\n");
3648
+ const renderAgentIssueComment = (config) => String.raw`echo "[agent] posting review comment to issue #$ISSUE_NUM..."
3649
+
3650
+ PR_BODY=""
3651
+ PR_BODY=$(su - ${config.sshUser} -c ". /run/docker-git/agent-env.sh 2>/dev/null; cd '$TARGET_DIR' && gh pr list --head '$REPO_REF' --json body --jq '.[0].body'" 2>/dev/null) || true
3652
+
3653
+ if [[ -z "$PR_BODY" ]]; then
3654
+ PR_BODY=$(su - ${config.sshUser} -c ". /run/docker-git/agent-env.sh 2>/dev/null; cd '$TARGET_DIR' && git log --format='%B' -1" 2>/dev/null) || true
3655
+ fi
3656
+
3657
+ if [[ -n "$PR_BODY" ]]; then
3658
+ COMMENT_FILE="/run/docker-git/agent-comment.txt"
3659
+ printf "%s" "$PR_BODY" > "$COMMENT_FILE"
3660
+ chmod 644 "$COMMENT_FILE"
3661
+ su - ${config.sshUser} -c ". /run/docker-git/agent-env.sh 2>/dev/null; cd '$TARGET_DIR' && gh issue comment '$ISSUE_NUM' --body-file '$COMMENT_FILE'" || echo "[agent] failed to comment on issue #$ISSUE_NUM"
3662
+ else
3663
+ echo "[agent] no PR body or commit message found, skipping comment"
3664
+ fi`;
3665
+ const renderProjectMoveScript = () => String.raw`#!/bin/bash
3666
+ . /run/docker-git/agent-env.sh 2>/dev/null || true
3667
+ cd "$1" || exit 1
3668
+ ISSUE_NUM="$2"
3669
+
3670
+ ISSUE_NODE_ID=$(gh issue view "$ISSUE_NUM" --json id --jq '.id' 2>/dev/null) || true
3671
+ if [[ -z "$ISSUE_NODE_ID" ]]; then
3672
+ echo "[agent] could not get issue node ID, skipping move"
3673
+ exit 0
3674
+ fi
3675
+
3676
+ GQL_QUERY='query($nodeId: ID!) { node(id: $nodeId) { ... on Issue { projectItems(first: 1) { nodes { id project { id field(name: "Status") { ... on ProjectV2SingleSelectField { id options { id name } } } } } } } } }'
3677
+ ALL_IDS=$(gh api graphql -F nodeId="$ISSUE_NODE_ID" -f query="$GQL_QUERY" \
3678
+ --jq '(.data.node.projectItems.nodes // [])[0] // empty | [.id, .project.id, .project.field.id, ([.project.field.options[] | select(.name | test("review"; "i"))][0].id)] | @tsv' 2>/dev/null) || true
3679
+
3680
+ if [[ -z "$ALL_IDS" ]]; then
3681
+ echo "[agent] issue #$ISSUE_NUM is not in a project board, skipping move"
3682
+ exit 0
3683
+ fi
3684
+
3685
+ ITEM_ID=$(printf "%s" "$ALL_IDS" | cut -f1)
3686
+ PROJECT_ID=$(printf "%s" "$ALL_IDS" | cut -f2)
3687
+ STATUS_FIELD_ID=$(printf "%s" "$ALL_IDS" | cut -f3)
3688
+ REVIEW_OPTION_ID=$(printf "%s" "$ALL_IDS" | cut -f4)
3689
+ if [[ -z "$STATUS_FIELD_ID" || -z "$REVIEW_OPTION_ID" || "$STATUS_FIELD_ID" == "null" || "$REVIEW_OPTION_ID" == "null" ]]; then
3690
+ echo "[agent] review status not found in project board, skipping move"
3691
+ exit 0
3692
+ fi
3693
+
3694
+ MUTATION='mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) { updateProjectV2ItemFieldValue(input: { projectId: $projectId, itemId: $itemId, fieldId: $fieldId, value: { singleSelectOptionId: $optionId } }) { projectV2Item { id } } }'
3695
+ MOVE_RESULT=$(gh api graphql \
3696
+ -F projectId="$PROJECT_ID" \
3697
+ -F itemId="$ITEM_ID" \
3698
+ -F fieldId="$STATUS_FIELD_ID" \
3699
+ -F optionId="$REVIEW_OPTION_ID" \
3700
+ -f query="$MUTATION" 2>&1) || true
3701
+
3702
+ if [[ "$MOVE_RESULT" == *"projectV2Item"* ]]; then
3703
+ echo "[agent] issue #$ISSUE_NUM moved to review"
3704
+ else
3705
+ echo "[agent] failed to move issue #$ISSUE_NUM in project board"
3706
+ fi`;
3707
+ const renderAgentIssueMove = (config) => [
3708
+ String.raw`echo "[agent] moving issue #$ISSUE_NUM to review..."
3709
+ MOVE_SCRIPT="/run/docker-git/project-move.sh"`,
3710
+ String.raw`cat > "$MOVE_SCRIPT" << 'EOFMOVE'
3711
+ ${renderProjectMoveScript()}
3712
+ EOFMOVE`,
3713
+ String.raw`chmod +x "$MOVE_SCRIPT"
3714
+ su - ${config.sshUser} -c "$MOVE_SCRIPT '$TARGET_DIR' '$ISSUE_NUM'" || true`
3715
+ ].join("\n");
3716
+ const renderAgentIssueReview = (config) => [
3717
+ String.raw`if [[ "$AGENT_OK" -eq 1 && "$AGENT_AUTO" == "1" && -n "$ISSUE_NUM" ]]; then`,
3718
+ indentBlock(renderAgentIssueComment(config)),
3719
+ "",
3720
+ renderAgentIssueMove(config),
3721
+ "fi"
3722
+ ].join("\n");
3723
+ const renderAgentFinalize = () => String.raw`if [[ "$AGENT_OK" -eq 1 ]]; then
3724
+ echo "[agent] done"
3725
+ touch "$AGENT_DONE_PATH"
3726
+ else
3727
+ echo "[agent] failed"
3728
+ touch "$AGENT_FAIL_PATH"
3729
+ fi`;
3730
+ const renderAgentLaunch = (config) => [
3731
+ String.raw`# 3) Auto-launch agent if AGENT_MODE is set
3732
+ if [[ "$CLONE_OK" -eq 1 && -n "$AGENT_MODE" ]]; then`,
3733
+ indentBlock(renderAgentSetup()),
3734
+ "",
3735
+ indentBlock(renderAgentModeCase(config)),
3736
+ "",
3737
+ renderAgentIssueReview(config),
3738
+ "",
3739
+ indentBlock(renderAgentFinalize()),
3740
+ "fi"
3741
+ ].join("\n");
3571
3742
  const renderEntrypointAutoUpdate = () => `# 1) Keep Codex CLI up to date if requested (bun only)
3572
3743
  if [[ "$CODEX_AUTO_UPDATE" == "1" ]]; then
3573
3744
  if command -v bun >/dev/null 2>&1; then
@@ -3744,6 +3915,8 @@ const renderEntrypointBackgroundTasks = (config) => `# 4) Start background tasks
3744
3915
  ${renderEntrypointAutoUpdate()}
3745
3916
 
3746
3917
  ${renderEntrypointClone(config)}
3918
+
3919
+ ${renderAgentLaunch(config)}
3747
3920
  ) &`;
3748
3921
  const renderEntrypoint = (config) => [
3749
3922
  renderEntrypointHeader(config),
@@ -3779,6 +3952,10 @@ const renderCodexAuthLabelEnv = (codexAuthLabel) => codexAuthLabel.length > 0 ?
3779
3952
  ` : "";
3780
3953
  const renderClaudeAuthLabelEnv = (claudeAuthLabel) => claudeAuthLabel.length > 0 ? ` CLAUDE_AUTH_LABEL: "${claudeAuthLabel}"
3781
3954
  ` : "";
3955
+ const renderAgentModeEnv = (agentMode) => agentMode !== void 0 && agentMode.length > 0 ? ` AGENT_MODE: "${agentMode}"
3956
+ ` : "";
3957
+ const renderAgentAutoEnv = (agentAuto) => agentAuto === true ? ` AGENT_AUTO: "1"
3958
+ ` : "";
3782
3959
  const buildPlaywrightFragments = (config, networkName) => {
3783
3960
  if (!config.enableMcpPlaywright) {
3784
3961
  return {
@@ -3831,6 +4008,8 @@ const buildComposeFragments = (config) => {
3831
4008
  const maybeGitTokenLabelEnv = renderGitTokenLabelEnv(gitTokenLabel);
3832
4009
  const maybeCodexAuthLabelEnv = renderCodexAuthLabelEnv(codexAuthLabel);
3833
4010
  const maybeClaudeAuthLabelEnv = renderClaudeAuthLabelEnv(claudeAuthLabel);
4011
+ const maybeAgentModeEnv = renderAgentModeEnv(config.agentMode);
4012
+ const maybeAgentAutoEnv = renderAgentAutoEnv(config.agentAuto);
3834
4013
  const playwright = buildPlaywrightFragments(config, networkName);
3835
4014
  return {
3836
4015
  networkMode,
@@ -3838,6 +4017,8 @@ const buildComposeFragments = (config) => {
3838
4017
  maybeGitTokenLabelEnv,
3839
4018
  maybeCodexAuthLabelEnv,
3840
4019
  maybeClaudeAuthLabelEnv,
4020
+ maybeAgentModeEnv,
4021
+ maybeAgentAutoEnv,
3841
4022
  maybeDependsOn: playwright.maybeDependsOn,
3842
4023
  maybePlaywrightEnv: playwright.maybePlaywrightEnv,
3843
4024
  maybeBrowserService: playwright.maybeBrowserService,
@@ -3856,7 +4037,7 @@ const renderComposeServices = (config, fragments) => `services:
3856
4037
  FORK_REPO_URL: "${fragments.forkRepoUrl}"
3857
4038
  ${fragments.maybeGitTokenLabelEnv} # Optional token label selector (maps to GITHUB_TOKEN__<LABEL>/GIT_AUTH_TOKEN__<LABEL>)
3858
4039
  ${fragments.maybeCodexAuthLabelEnv} # Optional Codex account label selector (maps to CODEX_AUTH_LABEL)
3859
- ${fragments.maybeClaudeAuthLabelEnv} # Optional Claude account label selector (maps to CLAUDE_AUTH_LABEL)
4040
+ ${fragments.maybeClaudeAuthLabelEnv}${fragments.maybeAgentModeEnv}${fragments.maybeAgentAutoEnv} # Optional Claude account label selector (maps to CLAUDE_AUTH_LABEL)
3860
4041
  TARGET_DIR: "${config.targetDir}"
3861
4042
  CODEX_HOME: "${config.codexHome}"
3862
4043
  ${fragments.maybePlaywrightEnv}${fragments.maybeDependsOn} env_file:
@@ -5407,8 +5588,11 @@ const listProjectStatus = Effect.asVoid(
5407
5588
  )
5408
5589
  );
5409
5590
  const clonePollInterval = Duration.seconds(1);
5591
+ const agentPollInterval = Duration.seconds(2);
5410
5592
  const cloneDonePath = "/run/docker-git/clone.done";
5411
5593
  const cloneFailPath = "/run/docker-git/clone.failed";
5594
+ const agentDonePath = "/run/docker-git/agent.done";
5595
+ const agentFailPath = "/run/docker-git/agent.failed";
5412
5596
  const logSshAccess = (baseDir, config) => Effect.gen(function* (_) {
5413
5597
  const fs = yield* _(FileSystem.FileSystem);
5414
5598
  const path = yield* _(Path.Path);
@@ -5467,6 +5651,47 @@ const waitForCloneCompletion = (cwd, config) => Effect.gen(function* (_) {
5467
5651
  );
5468
5652
  }
5469
5653
  });
5654
+ const checkAgentState = (cwd, containerName) => Effect.gen(function* (_) {
5655
+ const failed = yield* _(runDockerExecExitCode(cwd, containerName, ["test", "-f", agentFailPath]));
5656
+ if (failed === 0) {
5657
+ return "failed";
5658
+ }
5659
+ const done = yield* _(runDockerExecExitCode(cwd, containerName, ["test", "-f", agentDonePath]));
5660
+ return done === 0 ? "done" : "pending";
5661
+ });
5662
+ const waitForAgentCompletion = (cwd, config) => Effect.gen(function* (_) {
5663
+ const logsFiber = yield* _(
5664
+ runDockerComposeLogsFollow(cwd).pipe(
5665
+ Effect.tapError(
5666
+ (error) => Effect.logWarning(
5667
+ `docker compose logs --follow failed: ${error instanceof Error ? error.message : String(error)}`
5668
+ )
5669
+ ),
5670
+ Effect.fork
5671
+ )
5672
+ );
5673
+ const result = yield* _(
5674
+ checkAgentState(cwd, config.containerName).pipe(
5675
+ Effect.repeat(
5676
+ Schedule.addDelay(
5677
+ Schedule.recurUntil((state) => state !== "pending"),
5678
+ () => agentPollInterval
5679
+ )
5680
+ )
5681
+ )
5682
+ );
5683
+ yield* _(Fiber$1.interrupt(logsFiber));
5684
+ if (result === "failed") {
5685
+ return yield* _(
5686
+ Effect.fail(
5687
+ new AgentFailedError({
5688
+ agentMode: config.agentMode ?? "unknown",
5689
+ targetDir: config.targetDir
5690
+ })
5691
+ )
5692
+ );
5693
+ }
5694
+ });
5470
5695
  const runDockerComposeUpByMode = (resolvedOutDir, projectConfig, force, forceEnv) => Effect.gen(function* (_) {
5471
5696
  yield* _(ensureComposeNetworkReady(resolvedOutDir, projectConfig));
5472
5697
  if (force) {
@@ -5511,9 +5736,16 @@ const runDockerUpIfNeeded = (resolvedOutDir, projectConfig, options) => Effect.g
5511
5736
  yield* _(Effect.log("Streaming container logs until clone completes..."));
5512
5737
  yield* _(waitForCloneCompletion(resolvedOutDir, projectConfig));
5513
5738
  }
5739
+ if (options.waitForAgent) {
5740
+ yield* _(Effect.log("Waiting for agent to complete..."));
5741
+ yield* _(waitForAgentCompletion(resolvedOutDir, projectConfig));
5742
+ }
5514
5743
  yield* _(Effect.log("Docker environment is up"));
5515
5744
  yield* _(logSshAccess(resolvedOutDir, projectConfig));
5516
5745
  });
5746
+ const runDockerDownCleanup = (resolvedOutDir) => runDockerComposeDownVolumes(resolvedOutDir).pipe(
5747
+ Effect.tap(() => Effect.log("Container and volumes removed."))
5748
+ );
5517
5749
  const resolvePathFromBase = (path, baseDir, targetPath) => path.isAbsolute(targetPath) ? targetPath : path.resolve(baseDir, targetPath);
5518
5750
  const toPosixPath = (value) => value.replaceAll("\\", "/");
5519
5751
  const resolveDockerGitRootRelativePath = (path, projectsRoot, inputPath) => {
@@ -5712,7 +5944,7 @@ const formatStateSyncLabel = (repoUrl) => {
5712
5944
  return repoPath.length > 0 ? repoPath : repoUrl;
5713
5945
  };
5714
5946
  const isInteractiveTty = () => process.stdin.isTTY && process.stdout.isTTY;
5715
- const buildSshArgs = (config, sshKeyPath) => {
5947
+ const buildSshArgs = (config, sshKeyPath, remoteCommand) => {
5716
5948
  const args = [];
5717
5949
  if (sshKeyPath !== null) {
5718
5950
  args.push("-i", sshKeyPath);
@@ -5730,21 +5962,25 @@ const buildSshArgs = (config, sshKeyPath) => {
5730
5962
  String(config.sshPort),
5731
5963
  `${config.sshUser}@localhost`
5732
5964
  );
5965
+ if (remoteCommand !== void 0) {
5966
+ args.push(remoteCommand);
5967
+ }
5733
5968
  return args;
5734
5969
  };
5735
- const openSshBestEffort = (template) => Effect.gen(function* (_) {
5970
+ const openSshBestEffort = (template, remoteCommand) => Effect.gen(function* (_) {
5736
5971
  const fs = yield* _(FileSystem.FileSystem);
5737
5972
  const path = yield* _(Path.Path);
5738
5973
  const sshKey = yield* _(findSshPrivateKey(fs, path, process.cwd()));
5739
5974
  const sshCommand = buildSshCommand(template, sshKey);
5740
- yield* _(Effect.log(`Opening SSH: ${sshCommand}`));
5975
+ const remoteCommandLabel = remoteCommand === void 0 ? "" : ` (${remoteCommand})`;
5976
+ yield* _(Effect.log(`Opening SSH: ${sshCommand}${remoteCommandLabel}`));
5741
5977
  yield* _(ensureTerminalCursorVisible());
5742
5978
  yield* _(
5743
5979
  runCommandWithExitCodes(
5744
5980
  {
5745
5981
  cwd: process.cwd(),
5746
5982
  command: "ssh",
5747
- args: buildSshArgs(template, sshKey)
5983
+ args: buildSshArgs(template, sshKey, remoteCommand)
5748
5984
  },
5749
5985
  [0, 130],
5750
5986
  (exitCode) => new CommandFailedError({ command: "ssh", exitCode })
@@ -5757,6 +5993,23 @@ const openSshBestEffort = (template) => Effect.gen(function* (_) {
5757
5993
  onSuccess: () => Effect.void
5758
5994
  })
5759
5995
  );
5996
+ const resolveInteractiveRemoteCommand = (projectConfig, interactiveAgent) => interactiveAgent && projectConfig.agentMode !== void 0 ? `cd '${projectConfig.targetDir}' && ${projectConfig.agentMode}` : void 0;
5997
+ const maybeOpenSsh = (command, hasAgent, waitForAgent, projectConfig) => Effect.gen(function* (_) {
5998
+ const interactiveAgent = hasAgent && !waitForAgent;
5999
+ if (!command.openSsh || hasAgent && !interactiveAgent) {
6000
+ return;
6001
+ }
6002
+ if (!command.runUp) {
6003
+ yield* _(Effect.logWarning("Skipping SSH auto-open: docker compose up disabled (--no-up)."));
6004
+ return;
6005
+ }
6006
+ if (!isInteractiveTty()) {
6007
+ yield* _(Effect.logWarning("Skipping SSH auto-open: not running in an interactive TTY."));
6008
+ return;
6009
+ }
6010
+ const remoteCommand = resolveInteractiveRemoteCommand(projectConfig, interactiveAgent);
6011
+ yield* _(openSshBestEffort(projectConfig, remoteCommand));
6012
+ }).pipe(Effect.asVoid);
5760
6013
  const runCreateProject = (path, command) => Effect.gen(function* (_) {
5761
6014
  if (command.runUp) {
5762
6015
  yield* _(ensureDockerDaemonAccess(process.cwd()));
@@ -5773,10 +6026,13 @@ const runCreateProject = (path, command) => Effect.gen(function* (_) {
5773
6026
  })
5774
6027
  );
5775
6028
  yield* _(logCreatedProject(resolvedOutDir, createdFiles));
6029
+ const hasAgent = resolvedConfig.agentMode !== void 0;
6030
+ const waitForAgent = hasAgent && (resolvedConfig.agentAuto ?? false);
5776
6031
  yield* _(
5777
6032
  runDockerUpIfNeeded(resolvedOutDir, projectConfig, {
5778
6033
  runUp: command.runUp,
5779
6034
  waitForClone: command.waitForClone,
6035
+ waitForAgent,
5780
6036
  force: command.force,
5781
6037
  forceEnv: command.forceEnv
5782
6038
  })
@@ -5784,16 +6040,12 @@ const runCreateProject = (path, command) => Effect.gen(function* (_) {
5784
6040
  if (command.runUp) {
5785
6041
  yield* _(logDockerAccessInfo(resolvedOutDir, projectConfig));
5786
6042
  }
5787
- yield* _(autoSyncState(`chore(state): update ${formatStateSyncLabel(projectConfig.repoUrl)}`));
5788
- if (command.openSsh) {
5789
- if (!command.runUp) {
5790
- yield* _(Effect.logWarning("Skipping SSH auto-open: docker compose up disabled (--no-up)."));
5791
- } else if (isInteractiveTty()) {
5792
- yield* _(openSshBestEffort(projectConfig));
5793
- } else {
5794
- yield* _(Effect.logWarning("Skipping SSH auto-open: not running in an interactive TTY."));
5795
- }
6043
+ if (waitForAgent) {
6044
+ yield* _(Effect.log("Agent finished. Cleaning up container..."));
6045
+ yield* _(runDockerDownCleanup(resolvedOutDir));
5796
6046
  }
6047
+ yield* _(autoSyncState(`chore(state): update ${formatStateSyncLabel(projectConfig.repoUrl)}`));
6048
+ yield* _(maybeOpenSsh(command, hasAgent, waitForAgent, projectConfig));
5797
6049
  }).pipe(Effect.asVoid);
5798
6050
  const createProject = (command) => Path.Path.pipe(Effect.flatMap((path) => runCreateProject(path, command)));
5799
6051
  const trimEdgeUnderscores = (value) => {
@@ -7462,7 +7714,10 @@ const booleanFlagUpdaters = {
7462
7714
  "--wipe": (raw) => ({ ...raw, wipe: true }),
7463
7715
  "--no-wipe": (raw) => ({ ...raw, wipe: false }),
7464
7716
  "--web": (raw) => ({ ...raw, authWeb: true }),
7465
- "--include-default": (raw) => ({ ...raw, includeDefault: true })
7717
+ "--include-default": (raw) => ({ ...raw, includeDefault: true }),
7718
+ "--claude": (raw) => ({ ...raw, agentClaude: true }),
7719
+ "--codex": (raw) => ({ ...raw, agentCodex: true }),
7720
+ "--auto": (raw) => ({ ...raw, agentAuto: true })
7466
7721
  };
7467
7722
  const valueFlagUpdaters = {
7468
7723
  repoUrl: (raw, value) => ({ ...raw, repoUrl: value }),
@@ -7808,7 +8063,14 @@ const resolveCreateBehavior = (raw) => ({
7808
8063
  forceEnv: raw.forceEnv ?? false,
7809
8064
  enableMcpPlaywright: raw.enableMcpPlaywright ?? false
7810
8065
  });
8066
+ const resolveAgentMode = (raw) => {
8067
+ if (raw.agentClaude) return "claude";
8068
+ if (raw.agentCodex) return "codex";
8069
+ return void 0;
8070
+ };
7811
8071
  const buildTemplateConfig = ({
8072
+ agentAuto,
8073
+ agentMode,
7812
8074
  claudeAuthLabel,
7813
8075
  codexAuthLabel,
7814
8076
  dockerNetworkMode,
@@ -7840,7 +8102,9 @@ const buildTemplateConfig = ({
7840
8102
  dockerNetworkMode,
7841
8103
  dockerSharedNetworkName,
7842
8104
  enableMcpPlaywright,
7843
- pnpmVersion: defaultTemplateConfig.pnpmVersion
8105
+ pnpmVersion: defaultTemplateConfig.pnpmVersion,
8106
+ agentMode,
8107
+ agentAuto
7844
8108
  });
7845
8109
  const buildCreateCommand = (raw) => Either.gen(function* (_) {
7846
8110
  const repo = yield* _(resolveRepoBasics(raw));
@@ -7854,6 +8118,8 @@ const buildCreateCommand = (raw) => Either.gen(function* (_) {
7854
8118
  const dockerSharedNetworkName = yield* _(
7855
8119
  nonEmpty("--shared-network", raw.dockerSharedNetworkName, defaultTemplateConfig.dockerSharedNetworkName)
7856
8120
  );
8121
+ const agentMode = resolveAgentMode(raw);
8122
+ const agentAuto = raw.agentAuto ?? false;
7857
8123
  return {
7858
8124
  _tag: "Create",
7859
8125
  outDir: paths.outDir,
@@ -7871,7 +8137,9 @@ const buildCreateCommand = (raw) => Either.gen(function* (_) {
7871
8137
  gitTokenLabel,
7872
8138
  codexAuthLabel,
7873
8139
  claudeAuthLabel,
7874
- enableMcpPlaywright: behavior.enableMcpPlaywright
8140
+ enableMcpPlaywright: behavior.enableMcpPlaywright,
8141
+ agentMode,
8142
+ agentAuto
7875
8143
  })
7876
8144
  };
7877
8145
  });
@@ -8175,6 +8443,9 @@ Options:
8175
8443
  --up | --no-up Run docker compose up after init (default: --up)
8176
8444
  --ssh | --no-ssh Auto-open SSH after create/clone (default: clone=--ssh, create=--no-ssh)
8177
8445
  --mcp-playwright | --no-mcp-playwright Enable Playwright MCP + Chromium sidecar (default: --no-mcp-playwright)
8446
+ --claude Start Claude Code agent inside container after clone
8447
+ --codex Start Codex agent inside container after clone
8448
+ --auto Auto-execute: agent completes the task, creates PR and pushes (requires --claude or --codex)
8178
8449
  --force Overwrite existing files and wipe compose volumes (docker compose down -v)
8179
8450
  --force-env Reset project env defaults only (keep workspace volume/data)
8180
8451
  -h, --help Show this help
@@ -11100,6 +11371,7 @@ const program = pipe(
11100
11371
  Effect.catchTag("DockerAccessError", logWarningAndExit),
11101
11372
  Effect.catchTag("DockerCommandError", logWarningAndExit),
11102
11373
  Effect.catchTag("AuthError", logWarningAndExit),
11374
+ Effect.catchTag("AgentFailedError", logWarningAndExit),
11103
11375
  Effect.catchTag("CommandFailedError", logWarningAndExit),
11104
11376
  Effect.catchTag("ScrapArchiveNotFoundError", logErrorAndExit),
11105
11377
  Effect.catchTag("ScrapTargetDirUnsupportedError", logErrorAndExit),