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

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.
@@ -846,6 +846,109 @@ const renderError = (error) => {
846
846
  }
847
847
  return renderNonParseError(error);
848
848
  };
849
+ const resolveEnvValue = (key) => {
850
+ const value = process.env[key]?.trim();
851
+ return value && value.length > 0 ? value : null;
852
+ };
853
+ const trimTrailingSlash$1 = (value) => {
854
+ let end = value.length;
855
+ while (end > 0) {
856
+ const char = value[end - 1];
857
+ if (char !== "/" && char !== "\\") {
858
+ break;
859
+ }
860
+ end -= 1;
861
+ }
862
+ return value.slice(0, end);
863
+ };
864
+ const pathStartsWith = (candidate, prefix) => candidate === prefix || candidate.startsWith(`${prefix}/`) || candidate.startsWith(`${prefix}\\`);
865
+ const translatePathPrefix = (candidate, sourcePrefix, targetPrefix) => pathStartsWith(candidate, sourcePrefix) ? `${targetPrefix}${candidate.slice(sourcePrefix.length)}` : null;
866
+ const resolveContainerProjectsRoot = () => {
867
+ const explicit = resolveEnvValue("DOCKER_GIT_PROJECTS_ROOT");
868
+ if (explicit !== null) {
869
+ return explicit;
870
+ }
871
+ const home = resolveEnvValue("HOME") ?? resolveEnvValue("USERPROFILE");
872
+ return home === null ? null : `${trimTrailingSlash$1(home)}/.docker-git`;
873
+ };
874
+ const resolveProjectsRootHostOverride = () => resolveEnvValue("DOCKER_GIT_PROJECTS_ROOT_HOST");
875
+ const resolveCurrentContainerId = (cwd) => {
876
+ const fromEnv = resolveEnvValue("HOSTNAME");
877
+ if (fromEnv !== null) {
878
+ return Effect.succeed(fromEnv);
879
+ }
880
+ return runCommandCapture(
881
+ {
882
+ cwd,
883
+ command: "hostname",
884
+ args: []
885
+ },
886
+ [0],
887
+ () => new Error("hostname failed")
888
+ ).pipe(
889
+ Effect.map((value) => value.trim()),
890
+ Effect.orElseSucceed(() => ""),
891
+ Effect.map((value) => value.length > 0 ? value : null)
892
+ );
893
+ };
894
+ const parseDockerInspectMounts = (raw) => raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0).flatMap((line) => {
895
+ const separator = line.indexOf(" ");
896
+ if (separator <= 0 || separator >= line.length - 1) {
897
+ return [];
898
+ }
899
+ const source = line.slice(0, separator).trim();
900
+ const destination = line.slice(separator + 1).trim();
901
+ if (source.length === 0 || destination.length === 0) {
902
+ return [];
903
+ }
904
+ return [{ source, destination }];
905
+ });
906
+ const remapDockerBindHostPathFromMounts = (hostPath, mounts) => {
907
+ let match = null;
908
+ for (const mount of mounts) {
909
+ if (!pathStartsWith(hostPath, mount.destination)) {
910
+ continue;
911
+ }
912
+ if (match === null || mount.destination.length > match.destination.length) {
913
+ match = mount;
914
+ }
915
+ }
916
+ if (match === null) {
917
+ return hostPath;
918
+ }
919
+ return `${match.source}${hostPath.slice(match.destination.length)}`;
920
+ };
921
+ const resolveDockerVolumeHostPath = (cwd, hostPath) => Effect.gen(function* (_) {
922
+ const containerProjectsRoot = resolveContainerProjectsRoot();
923
+ const hostProjectsRoot = resolveProjectsRootHostOverride();
924
+ if (containerProjectsRoot !== null && hostProjectsRoot !== null) {
925
+ const remapped = translatePathPrefix(hostPath, containerProjectsRoot, hostProjectsRoot);
926
+ if (remapped !== null) {
927
+ return remapped;
928
+ }
929
+ }
930
+ const containerId = yield* _(resolveCurrentContainerId(cwd));
931
+ if (containerId === null) {
932
+ return hostPath;
933
+ }
934
+ const mountsJson = yield* _(
935
+ runCommandCapture(
936
+ {
937
+ cwd,
938
+ command: "docker",
939
+ args: [
940
+ "inspect",
941
+ containerId,
942
+ "--format",
943
+ String.raw`{{range .Mounts}}{{println .Source "\t" .Destination}}{{end}}`
944
+ ]
945
+ },
946
+ [0],
947
+ () => new Error("docker inspect current container failed")
948
+ ).pipe(Effect.orElseSucceed(() => ""))
949
+ );
950
+ return remapDockerBindHostPathFromMounts(hostPath, parseDockerInspectMounts(mountsJson));
951
+ });
849
952
  const resolveDefaultDockerUser = () => {
850
953
  const getUid = Reflect.get(process, "getuid");
851
954
  const getGid = Reflect.get(process, "getgid");
@@ -893,17 +996,44 @@ const buildDockerArgs = (spec) => {
893
996
  }
894
997
  return [...base, spec.image, ...spec.args];
895
998
  };
896
- const runDockerAuth = (spec, okExitCodes, onFailure) => runCommandWithExitCodes(
897
- { cwd: spec.cwd, command: "docker", args: buildDockerArgs(spec) },
898
- okExitCodes,
899
- onFailure
900
- );
901
- const runDockerAuthCapture = (spec, okExitCodes, onFailure) => runCommandCapture(
902
- { cwd: spec.cwd, command: "docker", args: buildDockerArgs(spec) },
903
- okExitCodes,
904
- onFailure
905
- );
906
- const runDockerAuthExitCode = (spec) => runCommandExitCode({ cwd: spec.cwd, command: "docker", args: buildDockerArgs(spec) });
999
+ const runDockerAuth = (spec, okExitCodes, onFailure) => Effect.gen(function* (_) {
1000
+ const hostPath = yield* _(resolveDockerVolumeHostPath(spec.cwd, spec.volume.hostPath));
1001
+ yield* _(
1002
+ runCommandWithExitCodes(
1003
+ {
1004
+ cwd: spec.cwd,
1005
+ command: "docker",
1006
+ args: buildDockerArgs({ ...spec, volume: { ...spec.volume, hostPath } })
1007
+ },
1008
+ okExitCodes,
1009
+ onFailure
1010
+ )
1011
+ );
1012
+ });
1013
+ const runDockerAuthCapture = (spec, okExitCodes, onFailure) => Effect.gen(function* (_) {
1014
+ const hostPath = yield* _(resolveDockerVolumeHostPath(spec.cwd, spec.volume.hostPath));
1015
+ return yield* _(
1016
+ runCommandCapture(
1017
+ {
1018
+ cwd: spec.cwd,
1019
+ command: "docker",
1020
+ args: buildDockerArgs({ ...spec, volume: { ...spec.volume, hostPath } })
1021
+ },
1022
+ okExitCodes,
1023
+ onFailure
1024
+ )
1025
+ );
1026
+ });
1027
+ const runDockerAuthExitCode = (spec) => Effect.gen(function* (_) {
1028
+ const hostPath = yield* _(resolveDockerVolumeHostPath(spec.cwd, spec.volume.hostPath));
1029
+ return yield* _(
1030
+ runCommandExitCode({
1031
+ cwd: spec.cwd,
1032
+ command: "docker",
1033
+ args: buildDockerArgs({ ...spec, volume: { ...spec.volume, hostPath } })
1034
+ })
1035
+ );
1036
+ });
907
1037
  const normalizeAccountLabel = (value, fallback) => {
908
1038
  const trimmed = value?.trim() ?? "";
909
1039
  if (trimmed.length === 0) {
@@ -2243,6 +2373,113 @@ EOF
2243
2373
  chmod 0644 "$DOCKER_GIT_SSHD_CONF" || true`;
2244
2374
  const renderEntrypointSshd = () => `# 5) Run sshd in foreground
2245
2375
  exec /usr/sbin/sshd -D`;
2376
+ const entrypointClaudeGlobalPromptTemplate = String.raw`# Claude Code: managed global memory (CLAUDE.md is auto-loaded by Claude Code)
2377
+ CLAUDE_GLOBAL_PROMPT_FILE="/home/__SSH_USER__/.claude/CLAUDE.md"
2378
+ CLAUDE_AUTO_SYSTEM_PROMPT="${"$"}{CLAUDE_AUTO_SYSTEM_PROMPT:-1}"
2379
+ CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: repository"
2380
+ REPO_REF_VALUE="${"$"}{REPO_REF:-__REPO_REF_DEFAULT__}"
2381
+ REPO_URL_VALUE="${"$"}{REPO_URL:-__REPO_URL_DEFAULT__}"
2382
+
2383
+ if [[ "$REPO_REF_VALUE" == issue-* ]]; then
2384
+ ISSUE_ID_VALUE="$(printf "%s" "$REPO_REF_VALUE" | sed -E 's#^issue-##')"
2385
+ ISSUE_URL_VALUE=""
2386
+ if [[ "$REPO_URL_VALUE" == https://github.com/* ]]; then
2387
+ ISSUE_REPO_VALUE="$(printf "%s" "$REPO_URL_VALUE" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')"
2388
+ if [[ -n "$ISSUE_REPO_VALUE" ]]; then
2389
+ ISSUE_URL_VALUE="https://github.com/$ISSUE_REPO_VALUE/issues/$ISSUE_ID_VALUE"
2390
+ fi
2391
+ fi
2392
+ if [[ -n "$ISSUE_URL_VALUE" ]]; then
2393
+ CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: issue #$ISSUE_ID_VALUE ($ISSUE_URL_VALUE)"
2394
+ else
2395
+ CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: issue #$ISSUE_ID_VALUE"
2396
+ fi
2397
+ elif [[ "$REPO_REF_VALUE" == refs/pull/*/head ]]; then
2398
+ PR_ID_VALUE="$(printf "%s" "$REPO_REF_VALUE" | sed -nE 's#^refs/pull/([0-9]+)/head$#\1#p')"
2399
+ PR_URL_VALUE=""
2400
+ if [[ "$REPO_URL_VALUE" == https://github.com/* && -n "$PR_ID_VALUE" ]]; then
2401
+ PR_REPO_VALUE="$(printf "%s" "$REPO_URL_VALUE" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')"
2402
+ if [[ -n "$PR_REPO_VALUE" ]]; then
2403
+ PR_URL_VALUE="https://github.com/$PR_REPO_VALUE/pull/$PR_ID_VALUE"
2404
+ fi
2405
+ fi
2406
+ if [[ -n "$PR_ID_VALUE" && -n "$PR_URL_VALUE" ]]; then
2407
+ CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: PR #$PR_ID_VALUE ($PR_URL_VALUE)"
2408
+ elif [[ -n "$PR_ID_VALUE" ]]; then
2409
+ CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: PR #$PR_ID_VALUE"
2410
+ else
2411
+ CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: pull request ($REPO_REF_VALUE)"
2412
+ fi
2413
+ fi
2414
+
2415
+ if [[ "$CLAUDE_AUTO_SYSTEM_PROMPT" == "1" ]]; then
2416
+ mkdir -p "$(dirname "$CLAUDE_GLOBAL_PROMPT_FILE")"
2417
+ chown 1000:1000 "$(dirname "$CLAUDE_GLOBAL_PROMPT_FILE")" 2>/dev/null || true
2418
+ if [[ ! -f "$CLAUDE_GLOBAL_PROMPT_FILE" ]] || grep -q "^<!-- docker-git-managed:claude-md -->$" "$CLAUDE_GLOBAL_PROMPT_FILE"; then
2419
+ cat <<EOF > "$CLAUDE_GLOBAL_PROMPT_FILE"
2420
+ <!-- docker-git-managed:claude-md -->
2421
+ Ты автономный агент, который имеет полностью все права управления контейнером. У тебя есть доступ к командам sudo, gh, codex, opencode, oh-my-opencode, claude, git, node, pnpm и всем остальным другим. Проекты с которыми идёт работа лежат по пути ~
2422
+ Рабочая папка проекта (git clone): __TARGET_DIR__
2423
+ Доступные workspace пути: __TARGET_DIR__
2424
+ $CLAUDE_WORKSPACE_CONTEXT
2425
+ Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__
2426
+ Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе.
2427
+ Если ты видишь файлы AGENTS.md или CLAUDE.md внутри проекта, ты обязан их читать и соблюдать инструкции.
2428
+ <!-- /docker-git-managed:claude-md -->
2429
+ EOF
2430
+ chmod 0644 "$CLAUDE_GLOBAL_PROMPT_FILE" || true
2431
+ chown 1000:1000 "$CLAUDE_GLOBAL_PROMPT_FILE" || true
2432
+ fi
2433
+ fi
2434
+
2435
+ export CLAUDE_AUTO_SYSTEM_PROMPT`;
2436
+ const escapeForDoubleQuotes$1 = (value) => {
2437
+ const backslash = String.fromCodePoint(92);
2438
+ const quote = String.fromCodePoint(34);
2439
+ const escapedBackslash = `${backslash}${backslash}`;
2440
+ const escapedQuote = `${backslash}${quote}`;
2441
+ return value.replaceAll(backslash, escapedBackslash).replaceAll(quote, escapedQuote);
2442
+ };
2443
+ const renderClaudeGlobalPromptSetup = (config) => entrypointClaudeGlobalPromptTemplate.replaceAll("__TARGET_DIR__", config.targetDir).replaceAll("__SSH_USER__", config.sshUser).replaceAll("__REPO_REF_DEFAULT__", escapeForDoubleQuotes$1(config.repoRef)).replaceAll("__REPO_URL_DEFAULT__", escapeForDoubleQuotes$1(config.repoUrl));
2444
+ const renderClaudeWrapperSetup = () => String.raw`CLAUDE_WRAPPER_BIN="/usr/local/bin/claude"
2445
+ if command -v claude >/dev/null 2>&1; then
2446
+ CURRENT_CLAUDE_BIN="$(command -v claude)"
2447
+ CLAUDE_REAL_DIR="$(dirname "$CURRENT_CLAUDE_BIN")"
2448
+ CLAUDE_REAL_BIN="$CLAUDE_REAL_DIR/.docker-git-claude-real"
2449
+
2450
+ # If a wrapper already exists but points to a missing real binary, recover from /usr/bin.
2451
+ if [[ "$CURRENT_CLAUDE_BIN" == "$CLAUDE_WRAPPER_BIN" && ! -e "$CLAUDE_REAL_BIN" && -x "/usr/bin/claude" ]]; then
2452
+ CURRENT_CLAUDE_BIN="/usr/bin/claude"
2453
+ CLAUDE_REAL_DIR="/usr/bin"
2454
+ CLAUDE_REAL_BIN="$CLAUDE_REAL_DIR/.docker-git-claude-real"
2455
+ fi
2456
+
2457
+ # Keep the "real" binary in the same directory as the original command to preserve relative symlinks.
2458
+ if [[ "$CURRENT_CLAUDE_BIN" != "$CLAUDE_REAL_BIN" && ! -e "$CLAUDE_REAL_BIN" ]]; then
2459
+ mv "$CURRENT_CLAUDE_BIN" "$CLAUDE_REAL_BIN"
2460
+ fi
2461
+ if [[ -e "$CLAUDE_REAL_BIN" ]]; then
2462
+ cat <<'EOF' > "$CLAUDE_WRAPPER_BIN"
2463
+ #!/usr/bin/env bash
2464
+ set -euo pipefail
2465
+
2466
+ CLAUDE_REAL_BIN="__CLAUDE_REAL_BIN__"
2467
+ CLAUDE_CONFIG_DIR="${"$"}{CLAUDE_CONFIG_DIR:-$HOME/.claude}"
2468
+ CLAUDE_TOKEN_FILE="$CLAUDE_CONFIG_DIR/.oauth-token"
2469
+
2470
+ if [[ -f "$CLAUDE_TOKEN_FILE" ]]; then
2471
+ CLAUDE_CODE_OAUTH_TOKEN="$(tr -d '\r\n' < "$CLAUDE_TOKEN_FILE")"
2472
+ export CLAUDE_CODE_OAUTH_TOKEN
2473
+ else
2474
+ unset CLAUDE_CODE_OAUTH_TOKEN || true
2475
+ fi
2476
+
2477
+ exec "$CLAUDE_REAL_BIN" "$@"
2478
+ EOF
2479
+ sed -i "s#__CLAUDE_REAL_BIN__#$CLAUDE_REAL_BIN#g" "$CLAUDE_WRAPPER_BIN" || true
2480
+ chmod 0755 "$CLAUDE_WRAPPER_BIN" || true
2481
+ fi
2482
+ fi`;
2246
2483
  const claudeAuthRootContainerPath = (sshUser) => `/home/${sshUser}/.docker-git/.orch/auth/claude`;
2247
2484
  const claudeAuthConfigTemplate = String.raw`# Claude Code: expose CLAUDE_CONFIG_DIR for SSH sessions (OAuth cache lives under ~/.docker-git/.orch/auth/claude)
2248
2485
  CLAUDE_LABEL_RAW="$CLAUDE_AUTH_LABEL"
@@ -2275,6 +2512,17 @@ mkdir -p "$CLAUDE_CONFIG_DIR" || true
2275
2512
  CLAUDE_HOME_DIR="__CLAUDE_HOME_DIR__"
2276
2513
  CLAUDE_HOME_JSON="__CLAUDE_HOME_JSON__"
2277
2514
  mkdir -p "$CLAUDE_HOME_DIR" || true
2515
+ CLAUDE_TOKEN_FILE="$CLAUDE_CONFIG_DIR/.oauth-token"
2516
+ CLAUDE_CREDENTIALS_FILE="$CLAUDE_CONFIG_DIR/.credentials.json"
2517
+ CLAUDE_NESTED_CREDENTIALS_FILE="$CLAUDE_CONFIG_DIR/.claude/.credentials.json"
2518
+
2519
+ docker_git_prepare_claude_auth_mode() {
2520
+ if [[ -s "$CLAUDE_TOKEN_FILE" ]]; then
2521
+ rm -f "$CLAUDE_CREDENTIALS_FILE" "$CLAUDE_NESTED_CREDENTIALS_FILE" "$CLAUDE_HOME_DIR/.credentials.json" || true
2522
+ fi
2523
+ }
2524
+
2525
+ docker_git_prepare_claude_auth_mode
2278
2526
 
2279
2527
  docker_git_link_claude_file() {
2280
2528
  local source_path="$1"
@@ -2302,17 +2550,13 @@ docker_git_link_claude_home_file() {
2302
2550
  docker_git_link_claude_home_file ".oauth-token"
2303
2551
  docker_git_link_claude_home_file ".config.json"
2304
2552
  docker_git_link_claude_home_file ".claude.json"
2305
- docker_git_link_claude_home_file ".credentials.json"
2553
+ if [[ ! -s "$CLAUDE_TOKEN_FILE" ]]; then
2554
+ docker_git_link_claude_home_file ".credentials.json"
2555
+ fi
2306
2556
  docker_git_link_claude_file "$CLAUDE_CONFIG_DIR/.claude.json" "$CLAUDE_HOME_JSON"
2307
2557
 
2308
- CLAUDE_TOKEN_FILE="$CLAUDE_CONFIG_DIR/.oauth-token"
2309
- CLAUDE_CREDENTIALS_FILE="$CLAUDE_CONFIG_DIR/.credentials.json"
2310
2558
  docker_git_refresh_claude_oauth_token() {
2311
2559
  local token=""
2312
- if [[ -s "$CLAUDE_CREDENTIALS_FILE" ]]; then
2313
- unset CLAUDE_CODE_OAUTH_TOKEN || true
2314
- return 0
2315
- fi
2316
2560
  if [[ -f "$CLAUDE_TOKEN_FILE" ]]; then
2317
2561
  token="$(tr -d '\r\n' < "$CLAUDE_TOKEN_FILE")"
2318
2562
  fi
@@ -2366,6 +2610,51 @@ EOF
2366
2610
  }
2367
2611
 
2368
2612
  docker_git_ensure_claude_cli`;
2613
+ const renderClaudePermissionSettingsConfig = () => String.raw`# Claude Code: keep permission settings in sync with docker-git defaults
2614
+ CLAUDE_PERMISSION_SETTINGS_FILE="$CLAUDE_CONFIG_DIR/settings.json"
2615
+ docker_git_sync_claude_permissions() {
2616
+ CLAUDE_PERMISSION_SETTINGS_FILE="$CLAUDE_PERMISSION_SETTINGS_FILE" node - <<'NODE'
2617
+ const fs = require("node:fs")
2618
+ const path = require("node:path")
2619
+
2620
+ const settingsPath = process.env.CLAUDE_PERMISSION_SETTINGS_FILE
2621
+ if (typeof settingsPath !== "string" || settingsPath.length === 0) {
2622
+ process.exit(0)
2623
+ }
2624
+
2625
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value)
2626
+
2627
+ let settings = {}
2628
+ try {
2629
+ const raw = fs.readFileSync(settingsPath, "utf8")
2630
+ const parsed = JSON.parse(raw)
2631
+ settings = isRecord(parsed) ? parsed : {}
2632
+ } catch {
2633
+ settings = {}
2634
+ }
2635
+
2636
+ const currentPermissions = isRecord(settings.permissions) ? settings.permissions : {}
2637
+ const nextPermissions = {
2638
+ ...currentPermissions,
2639
+ defaultMode: "bypassPermissions"
2640
+ }
2641
+ const nextSettings = {
2642
+ ...settings,
2643
+ permissions: nextPermissions
2644
+ }
2645
+
2646
+ if (JSON.stringify(settings) === JSON.stringify(nextSettings)) {
2647
+ process.exit(0)
2648
+ }
2649
+
2650
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true })
2651
+ fs.writeFileSync(settingsPath, JSON.stringify(nextSettings, null, 2) + "\n", { mode: 0o600 })
2652
+ NODE
2653
+ }
2654
+
2655
+ docker_git_sync_claude_permissions
2656
+ chmod 0600 "$CLAUDE_PERMISSION_SETTINGS_FILE" 2>/dev/null || true
2657
+ chown 1000:1000 "$CLAUDE_PERMISSION_SETTINGS_FILE" 2>/dev/null || true`;
2369
2658
  const renderClaudeMcpPlaywrightConfig = () => String.raw`# Claude Code: keep Playwright MCP config in sync with container settings
2370
2659
  CLAUDE_SETTINGS_FILE="${"$"}{CLAUDE_HOME_JSON:-$CLAUDE_CONFIG_DIR/.claude.json}"
2371
2660
  docker_git_sync_claude_playwright_mcp() {
@@ -2421,126 +2710,13 @@ NODE
2421
2710
 
2422
2711
  docker_git_sync_claude_playwright_mcp
2423
2712
  chown 1000:1000 "$CLAUDE_SETTINGS_FILE" 2>/dev/null || true`;
2424
- const entrypointClaudeGlobalPromptTemplate = String.raw`# Claude Code: managed global memory (CLAUDE.md is auto-loaded by Claude Code)
2425
- CLAUDE_GLOBAL_PROMPT_FILE="/home/__SSH_USER__/.claude/CLAUDE.md"
2426
- CLAUDE_AUTO_SYSTEM_PROMPT="${"$"}{CLAUDE_AUTO_SYSTEM_PROMPT:-1}"
2427
- CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: repository"
2428
- REPO_REF_VALUE="${"$"}{REPO_REF:-__REPO_REF_DEFAULT__}"
2429
- REPO_URL_VALUE="${"$"}{REPO_URL:-__REPO_URL_DEFAULT__}"
2430
-
2431
- if [[ "$REPO_REF_VALUE" == issue-* ]]; then
2432
- ISSUE_ID_VALUE="$(printf "%s" "$REPO_REF_VALUE" | sed -E 's#^issue-##')"
2433
- ISSUE_URL_VALUE=""
2434
- if [[ "$REPO_URL_VALUE" == https://github.com/* ]]; then
2435
- ISSUE_REPO_VALUE="$(printf "%s" "$REPO_URL_VALUE" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')"
2436
- if [[ -n "$ISSUE_REPO_VALUE" ]]; then
2437
- ISSUE_URL_VALUE="https://github.com/$ISSUE_REPO_VALUE/issues/$ISSUE_ID_VALUE"
2438
- fi
2439
- fi
2440
- if [[ -n "$ISSUE_URL_VALUE" ]]; then
2441
- CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: issue #$ISSUE_ID_VALUE ($ISSUE_URL_VALUE)"
2442
- else
2443
- CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: issue #$ISSUE_ID_VALUE"
2444
- fi
2445
- elif [[ "$REPO_REF_VALUE" == refs/pull/*/head ]]; then
2446
- PR_ID_VALUE="$(printf "%s" "$REPO_REF_VALUE" | sed -nE 's#^refs/pull/([0-9]+)/head$#\1#p')"
2447
- PR_URL_VALUE=""
2448
- if [[ "$REPO_URL_VALUE" == https://github.com/* && -n "$PR_ID_VALUE" ]]; then
2449
- PR_REPO_VALUE="$(printf "%s" "$REPO_URL_VALUE" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')"
2450
- if [[ -n "$PR_REPO_VALUE" ]]; then
2451
- PR_URL_VALUE="https://github.com/$PR_REPO_VALUE/pull/$PR_ID_VALUE"
2452
- fi
2453
- fi
2454
- if [[ -n "$PR_ID_VALUE" && -n "$PR_URL_VALUE" ]]; then
2455
- CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: PR #$PR_ID_VALUE ($PR_URL_VALUE)"
2456
- elif [[ -n "$PR_ID_VALUE" ]]; then
2457
- CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: PR #$PR_ID_VALUE"
2458
- else
2459
- CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: pull request ($REPO_REF_VALUE)"
2460
- fi
2461
- fi
2462
-
2463
- if [[ "$CLAUDE_AUTO_SYSTEM_PROMPT" == "1" ]]; then
2464
- mkdir -p "$(dirname "$CLAUDE_GLOBAL_PROMPT_FILE")"
2465
- chown 1000:1000 "$(dirname "$CLAUDE_GLOBAL_PROMPT_FILE")" 2>/dev/null || true
2466
- if [[ ! -f "$CLAUDE_GLOBAL_PROMPT_FILE" ]] || grep -q "^<!-- docker-git-managed:claude-md -->$" "$CLAUDE_GLOBAL_PROMPT_FILE"; then
2467
- cat <<EOF > "$CLAUDE_GLOBAL_PROMPT_FILE"
2468
- <!-- docker-git-managed:claude-md -->
2469
- Ты автономный агент, который имеет полностью все права управления контейнером. У тебя есть доступ к командам sudo, gh, codex, opencode, oh-my-opencode, claude, git, node, pnpm и всем остальным другим. Проекты с которыми идёт работа лежат по пути ~
2470
- Рабочая папка проекта (git clone): __TARGET_DIR__
2471
- Доступные workspace пути: __TARGET_DIR__
2472
- $CLAUDE_WORKSPACE_CONTEXT
2473
- Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__
2474
- Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе.
2475
- Если ты видишь файлы AGENTS.md или CLAUDE.md внутри проекта, ты обязан их читать и соблюдать инструкции.
2476
- <!-- /docker-git-managed:claude-md -->
2477
- EOF
2478
- chmod 0644 "$CLAUDE_GLOBAL_PROMPT_FILE" || true
2479
- chown 1000:1000 "$CLAUDE_GLOBAL_PROMPT_FILE" || true
2480
- fi
2481
- fi
2482
-
2483
- export CLAUDE_AUTO_SYSTEM_PROMPT`;
2484
- const escapeForDoubleQuotes$1 = (value) => {
2485
- const backslash = String.fromCodePoint(92);
2486
- const quote = String.fromCodePoint(34);
2487
- const escapedBackslash = `${backslash}${backslash}`;
2488
- const escapedQuote = `${backslash}${quote}`;
2489
- return value.replaceAll(backslash, escapedBackslash).replaceAll(quote, escapedQuote);
2490
- };
2491
- const renderClaudeGlobalPromptSetup = (config) => entrypointClaudeGlobalPromptTemplate.replaceAll("__TARGET_DIR__", config.targetDir).replaceAll("__SSH_USER__", config.sshUser).replaceAll("__REPO_REF_DEFAULT__", escapeForDoubleQuotes$1(config.repoRef)).replaceAll("__REPO_URL_DEFAULT__", escapeForDoubleQuotes$1(config.repoUrl));
2492
- const renderClaudeWrapperSetup = () => String.raw`CLAUDE_WRAPPER_BIN="/usr/local/bin/claude"
2493
- if command -v claude >/dev/null 2>&1; then
2494
- CURRENT_CLAUDE_BIN="$(command -v claude)"
2495
- CLAUDE_REAL_DIR="$(dirname "$CURRENT_CLAUDE_BIN")"
2496
- CLAUDE_REAL_BIN="$CLAUDE_REAL_DIR/.docker-git-claude-real"
2497
-
2498
- # If a wrapper already exists but points to a missing real binary, recover from /usr/bin.
2499
- if [[ "$CURRENT_CLAUDE_BIN" == "$CLAUDE_WRAPPER_BIN" && ! -e "$CLAUDE_REAL_BIN" && -x "/usr/bin/claude" ]]; then
2500
- CURRENT_CLAUDE_BIN="/usr/bin/claude"
2501
- CLAUDE_REAL_DIR="/usr/bin"
2502
- CLAUDE_REAL_BIN="$CLAUDE_REAL_DIR/.docker-git-claude-real"
2503
- fi
2504
-
2505
- # Keep the "real" binary in the same directory as the original command to preserve relative symlinks.
2506
- if [[ "$CURRENT_CLAUDE_BIN" != "$CLAUDE_REAL_BIN" && ! -e "$CLAUDE_REAL_BIN" ]]; then
2507
- mv "$CURRENT_CLAUDE_BIN" "$CLAUDE_REAL_BIN"
2508
- fi
2509
- if [[ -e "$CLAUDE_REAL_BIN" ]]; then
2510
- cat <<'EOF' > "$CLAUDE_WRAPPER_BIN"
2511
- #!/usr/bin/env bash
2512
- set -euo pipefail
2513
-
2514
- CLAUDE_REAL_BIN="__CLAUDE_REAL_BIN__"
2515
- CLAUDE_CONFIG_DIR="${"$"}{CLAUDE_CONFIG_DIR:-$HOME/.claude}"
2516
- CLAUDE_TOKEN_FILE="$CLAUDE_CONFIG_DIR/.oauth-token"
2517
- CLAUDE_CREDENTIALS_FILE="$CLAUDE_CONFIG_DIR/.credentials.json"
2518
-
2519
- if [[ -s "$CLAUDE_CREDENTIALS_FILE" ]]; then
2520
- unset CLAUDE_CODE_OAUTH_TOKEN || true
2521
- elif [[ -f "$CLAUDE_TOKEN_FILE" ]]; then
2522
- CLAUDE_CODE_OAUTH_TOKEN="$(tr -d '\r\n' < "$CLAUDE_TOKEN_FILE")"
2523
- export CLAUDE_CODE_OAUTH_TOKEN
2524
- else
2525
- unset CLAUDE_CODE_OAUTH_TOKEN || true
2526
- fi
2527
-
2528
- exec "$CLAUDE_REAL_BIN" "$@"
2529
- EOF
2530
- sed -i "s#__CLAUDE_REAL_BIN__#$CLAUDE_REAL_BIN#g" "$CLAUDE_WRAPPER_BIN" || true
2531
- chmod 0755 "$CLAUDE_WRAPPER_BIN" || true
2532
- fi
2533
- fi`;
2534
2713
  const renderClaudeProfileSetup = () => String.raw`CLAUDE_PROFILE="/etc/profile.d/claude-config.sh"
2535
2714
  printf "export CLAUDE_AUTH_LABEL=%q\n" "$CLAUDE_AUTH_LABEL" > "$CLAUDE_PROFILE"
2536
2715
  printf "export CLAUDE_CONFIG_DIR=%q\n" "$CLAUDE_CONFIG_DIR" >> "$CLAUDE_PROFILE"
2537
2716
  printf "export CLAUDE_AUTO_SYSTEM_PROMPT=%q\n" "$CLAUDE_AUTO_SYSTEM_PROMPT" >> "$CLAUDE_PROFILE"
2538
2717
  cat <<'EOF' >> "$CLAUDE_PROFILE"
2539
2718
  CLAUDE_TOKEN_FILE="${"$"}{CLAUDE_CONFIG_DIR:-$HOME/.claude}/.oauth-token"
2540
- CLAUDE_CREDENTIALS_FILE="${"$"}{CLAUDE_CONFIG_DIR:-$HOME/.claude}/.credentials.json"
2541
- if [[ -s "$CLAUDE_CREDENTIALS_FILE" ]]; then
2542
- unset CLAUDE_CODE_OAUTH_TOKEN || true
2543
- elif [[ -f "$CLAUDE_TOKEN_FILE" ]]; then
2719
+ if [[ -f "$CLAUDE_TOKEN_FILE" ]]; then
2544
2720
  export CLAUDE_CODE_OAUTH_TOKEN="$(tr -d '\r\n' < "$CLAUDE_TOKEN_FILE")"
2545
2721
  else
2546
2722
  unset CLAUDE_CODE_OAUTH_TOKEN || true
@@ -2555,6 +2731,7 @@ docker_git_upsert_ssh_env "CLAUDE_AUTO_SYSTEM_PROMPT" "$CLAUDE_AUTO_SYSTEM_PROMP
2555
2731
  const renderEntrypointClaudeConfig = (config) => [
2556
2732
  renderClaudeAuthConfig(config),
2557
2733
  renderClaudeCliInstall(),
2734
+ renderClaudePermissionSettingsConfig(),
2558
2735
  renderClaudeMcpPlaywrightConfig(),
2559
2736
  renderClaudeGlobalPromptSetup(config),
2560
2737
  renderClaudeWrapperSetup(),
@@ -4668,70 +4845,6 @@ const parseJsonRecord = (text) => Either.match(ParseResult.decodeUnknownEither(J
4668
4845
  const hasClaudeOauthAccount = (record) => record !== null && typeof record["oauthAccount"] === "object" && record["oauthAccount"] !== null;
4669
4846
  const hasClaudeCredentials = (record) => record !== null && typeof record["claudeAiOauth"] === "object" && record["claudeAiOauth"] !== null;
4670
4847
  const isGithubTokenKey = (key) => key === "GITHUB_TOKEN" || key === "GH_TOKEN" || key.startsWith("GITHUB_TOKEN__");
4671
- const syncGithubAuthKeys = (sourceText, targetText) => {
4672
- const sourceTokenEntries = parseEnvEntries(sourceText).filter((entry) => isGithubTokenKey(entry.key));
4673
- if (sourceTokenEntries.length === 0) {
4674
- return targetText;
4675
- }
4676
- const targetTokenKeys = parseEnvEntries(targetText).filter((entry) => isGithubTokenKey(entry.key)).map((entry) => entry.key);
4677
- let next = targetText;
4678
- for (const key of targetTokenKeys) {
4679
- next = removeEnvKey(next, key);
4680
- }
4681
- for (const entry of sourceTokenEntries) {
4682
- next = upsertEnvKey(next, entry.key, entry.value);
4683
- }
4684
- return next;
4685
- };
4686
- const syncGithubTokenKeysInFile = (sourcePath, targetPath) => withFsPathContext(
4687
- ({ fs }) => Effect.gen(function* (_) {
4688
- const sourceExists = yield* _(fs.exists(sourcePath));
4689
- if (!sourceExists) {
4690
- return;
4691
- }
4692
- const targetExists = yield* _(fs.exists(targetPath));
4693
- if (!targetExists) {
4694
- return;
4695
- }
4696
- const sourceInfo = yield* _(fs.stat(sourcePath));
4697
- const targetInfo = yield* _(fs.stat(targetPath));
4698
- if (sourceInfo.type !== "File" || targetInfo.type !== "File") {
4699
- return;
4700
- }
4701
- const sourceText = yield* _(fs.readFileString(sourcePath));
4702
- const targetText = yield* _(fs.readFileString(targetPath));
4703
- const mergedText = syncGithubAuthKeys(sourceText, targetText);
4704
- if (mergedText !== targetText) {
4705
- yield* _(fs.writeFileString(targetPath, mergedText));
4706
- yield* _(Effect.log(`Synced GitHub auth keys from ${sourcePath} to ${targetPath}`));
4707
- }
4708
- })
4709
- );
4710
- const copyFileIfNeeded = (sourcePath, targetPath) => withFsPathContext(
4711
- ({ fs, path }) => Effect.gen(function* (_) {
4712
- const sourceExists = yield* _(fs.exists(sourcePath));
4713
- if (!sourceExists) {
4714
- return;
4715
- }
4716
- const sourceInfo = yield* _(fs.stat(sourcePath));
4717
- if (sourceInfo.type !== "File") {
4718
- return;
4719
- }
4720
- yield* _(fs.makeDirectory(path.dirname(targetPath), { recursive: true }));
4721
- const targetExists = yield* _(fs.exists(targetPath));
4722
- if (!targetExists) {
4723
- yield* _(fs.copyFile(sourcePath, targetPath));
4724
- yield* _(Effect.log(`Copied env file from ${sourcePath} to ${targetPath}`));
4725
- return;
4726
- }
4727
- const sourceText = yield* _(fs.readFileString(sourcePath));
4728
- const targetText = yield* _(fs.readFileString(targetPath));
4729
- if (shouldCopyEnv(sourceText, targetText) === "copy") {
4730
- yield* _(fs.writeFileString(targetPath, sourceText));
4731
- yield* _(Effect.log(`Synced env file from ${sourcePath} to ${targetPath}`));
4732
- }
4733
- })
4734
- );
4735
4848
  const syncClaudeJsonFile = (fs, path, spec) => Effect.gen(function* (_) {
4736
4849
  const sourceExists = yield* _(fs.exists(spec.sourcePath));
4737
4850
  if (!sourceExists) {
@@ -4782,6 +4895,18 @@ const syncClaudeCredentialsJson = (fs, path, sourcePath, targetPath) => syncClau
4782
4895
  seedLabel: "Claude credentials",
4783
4896
  updateLabel: "Claude credentials"
4784
4897
  });
4898
+ const hasNonEmptyFile = (fs, filePath) => Effect.gen(function* (_) {
4899
+ const exists = yield* _(fs.exists(filePath));
4900
+ if (!exists) {
4901
+ return false;
4902
+ }
4903
+ const info = yield* _(fs.stat(filePath));
4904
+ if (info.type !== "File") {
4905
+ return false;
4906
+ }
4907
+ const text = yield* _(fs.readFileString(filePath), Effect.orElseSucceed(() => ""));
4908
+ return text.trim().length > 0;
4909
+ });
4785
4910
  const ensureClaudeAuthSeedFromHome = (baseDir, claudeAuthPath) => withFsPathContext(
4786
4911
  ({ fs, path }) => Effect.gen(function* (_) {
4787
4912
  const homeDir = (process.env["HOME"] ?? "").trim();
@@ -4793,10 +4918,78 @@ const ensureClaudeAuthSeedFromHome = (baseDir, claudeAuthPath) => withFsPathCont
4793
4918
  const claudeRoot = resolvePathFromBase$1(path, baseDir, claudeAuthPath);
4794
4919
  const targetAccountDir = path.join(claudeRoot, "default");
4795
4920
  const targetClaudeJson = path.join(targetAccountDir, ".claude.json");
4921
+ const targetOauthToken = path.join(targetAccountDir, ".oauth-token");
4796
4922
  const targetCredentials = path.join(targetAccountDir, ".credentials.json");
4923
+ const hasTargetOauthToken = yield* _(hasNonEmptyFile(fs, targetOauthToken));
4797
4924
  yield* _(fs.makeDirectory(targetAccountDir, { recursive: true }));
4798
4925
  yield* _(syncClaudeHomeJson(fs, path, sourceClaudeJson, targetClaudeJson));
4799
- yield* _(syncClaudeCredentialsJson(fs, path, sourceCredentials, targetCredentials));
4926
+ if (!hasTargetOauthToken) {
4927
+ yield* _(syncClaudeCredentialsJson(fs, path, sourceCredentials, targetCredentials));
4928
+ }
4929
+ })
4930
+ );
4931
+ const syncGithubAuthKeys = (sourceText, targetText) => {
4932
+ const sourceTokenEntries = parseEnvEntries(sourceText).filter((entry) => isGithubTokenKey(entry.key));
4933
+ if (sourceTokenEntries.length === 0) {
4934
+ return targetText;
4935
+ }
4936
+ const targetTokenKeys = parseEnvEntries(targetText).filter((entry) => isGithubTokenKey(entry.key)).map((entry) => entry.key);
4937
+ let next = targetText;
4938
+ for (const key of targetTokenKeys) {
4939
+ next = removeEnvKey(next, key);
4940
+ }
4941
+ for (const entry of sourceTokenEntries) {
4942
+ next = upsertEnvKey(next, entry.key, entry.value);
4943
+ }
4944
+ return next;
4945
+ };
4946
+ const syncGithubTokenKeysInFile = (sourcePath, targetPath) => withFsPathContext(
4947
+ ({ fs }) => Effect.gen(function* (_) {
4948
+ const sourceExists = yield* _(fs.exists(sourcePath));
4949
+ if (!sourceExists) {
4950
+ return;
4951
+ }
4952
+ const targetExists = yield* _(fs.exists(targetPath));
4953
+ if (!targetExists) {
4954
+ return;
4955
+ }
4956
+ const sourceInfo = yield* _(fs.stat(sourcePath));
4957
+ const targetInfo = yield* _(fs.stat(targetPath));
4958
+ if (sourceInfo.type !== "File" || targetInfo.type !== "File") {
4959
+ return;
4960
+ }
4961
+ const sourceText = yield* _(fs.readFileString(sourcePath));
4962
+ const targetText = yield* _(fs.readFileString(targetPath));
4963
+ const mergedText = syncGithubAuthKeys(sourceText, targetText);
4964
+ if (mergedText !== targetText) {
4965
+ yield* _(fs.writeFileString(targetPath, mergedText));
4966
+ yield* _(Effect.log(`Synced GitHub auth keys from ${sourcePath} to ${targetPath}`));
4967
+ }
4968
+ })
4969
+ );
4970
+ const copyFileIfNeeded = (sourcePath, targetPath) => withFsPathContext(
4971
+ ({ fs, path }) => Effect.gen(function* (_) {
4972
+ const sourceExists = yield* _(fs.exists(sourcePath));
4973
+ if (!sourceExists) {
4974
+ return;
4975
+ }
4976
+ const sourceInfo = yield* _(fs.stat(sourcePath));
4977
+ if (sourceInfo.type !== "File") {
4978
+ return;
4979
+ }
4980
+ yield* _(fs.makeDirectory(path.dirname(targetPath), { recursive: true }));
4981
+ const targetExists = yield* _(fs.exists(targetPath));
4982
+ if (!targetExists) {
4983
+ yield* _(fs.copyFile(sourcePath, targetPath));
4984
+ yield* _(Effect.log(`Copied env file from ${sourcePath} to ${targetPath}`));
4985
+ return;
4986
+ }
4987
+ const sourceText = yield* _(fs.readFileString(sourcePath));
4988
+ const targetText = yield* _(fs.readFileString(targetPath));
4989
+ if (shouldCopyEnv(sourceText, targetText) === "copy") {
4990
+ yield* _(fs.writeFileString(targetPath, sourceText));
4991
+ yield* _(Effect.log(`Synced env file from ${sourcePath} to ${targetPath}`));
4992
+ }
4800
4993
  })
4801
4994
  );
4802
4995
  const ensureCodexConfigFile = (baseDir, codexAuthPath) => withFsPathContext(
@@ -6057,7 +6250,8 @@ const runClaudeOauthLoginWithPrompt = (cwd, accountPath, options) => {
6057
6250
  return Effect.scoped(
6058
6251
  Effect.gen(function* (_) {
6059
6252
  const executor = yield* _(CommandExecutor.CommandExecutor);
6060
- const spec = buildDockerSetupTokenSpec(cwd, accountPath, options.image, options.containerPath);
6253
+ const hostPath = yield* _(resolveDockerVolumeHostPath(cwd, accountPath));
6254
+ const spec = buildDockerSetupTokenSpec(cwd, hostPath, options.image, options.containerPath);
6061
6255
  const proc = yield* _(startDockerProcess(executor, spec));
6062
6256
  const tokenBox = { value: null };
6063
6257
  const stdoutFiber = yield* _(Effect.forkScoped(pumpDockerOutput(proc.stdout, 1, tokenBox)));
@@ -6106,6 +6300,10 @@ const syncClaudeCredentialsFile = (fs, accountPath) => Effect.gen(function* (_)
6106
6300
  yield* _(fs.chmod(nestedPath, 384), Effect.orElseSucceed(() => void 0));
6107
6301
  }
6108
6302
  });
6303
+ const clearClaudeSessionCredentials = (fs, accountPath) => Effect.gen(function* (_) {
6304
+ yield* _(fs.remove(claudeCredentialsPath(accountPath), { force: true }));
6305
+ yield* _(fs.remove(claudeNestedCredentialsPath(accountPath), { force: true }));
6306
+ });
6109
6307
  const hasNonEmptyOauthToken$1 = (fs, accountPath) => Effect.gen(function* (_) {
6110
6308
  const tokenPath = claudeOauthTokenPath(accountPath);
6111
6309
  const hasToken = yield* _(isRegularFile(fs, tokenPath));
@@ -6115,7 +6313,30 @@ const hasNonEmptyOauthToken$1 = (fs, accountPath) => Effect.gen(function* (_) {
6115
6313
  const tokenText = yield* _(fs.readFileString(tokenPath), Effect.orElseSucceed(() => ""));
6116
6314
  return tokenText.trim().length > 0;
6117
6315
  });
6118
- const buildClaudeAuthEnv = (interactive) => interactive ? [`HOME=${claudeContainerHomeDir}`, `CLAUDE_CONFIG_DIR=${claudeContainerHomeDir}`, "BROWSER=echo"] : [`HOME=${claudeContainerHomeDir}`, `CLAUDE_CONFIG_DIR=${claudeContainerHomeDir}`];
6316
+ const readOauthToken = (fs, accountPath) => Effect.gen(function* (_) {
6317
+ const tokenPath = claudeOauthTokenPath(accountPath);
6318
+ const hasToken = yield* _(isRegularFile(fs, tokenPath));
6319
+ if (!hasToken) {
6320
+ return null;
6321
+ }
6322
+ const tokenText = yield* _(fs.readFileString(tokenPath), Effect.orElseSucceed(() => ""));
6323
+ const token = tokenText.trim();
6324
+ return token.length > 0 ? token : null;
6325
+ });
6326
+ const resolveClaudeAuthMethod = (fs, accountPath) => Effect.gen(function* (_) {
6327
+ const hasOauthToken = yield* _(hasNonEmptyOauthToken$1(fs, accountPath));
6328
+ if (hasOauthToken) {
6329
+ yield* _(clearClaudeSessionCredentials(fs, accountPath));
6330
+ return "oauth-token";
6331
+ }
6332
+ yield* _(syncClaudeCredentialsFile(fs, accountPath));
6333
+ const hasCredentials = yield* _(isRegularFile(fs, claudeCredentialsPath(accountPath)));
6334
+ return hasCredentials ? "claude-ai-session" : "none";
6335
+ });
6336
+ const buildClaudeAuthEnv = (interactive, oauthToken = null) => [
6337
+ ...[`HOME=${claudeContainerHomeDir}`, `CLAUDE_CONFIG_DIR=${claudeContainerHomeDir}`],
6338
+ ...oauthToken === null ? [] : [`CLAUDE_CODE_OAUTH_TOKEN=${oauthToken}`]
6339
+ ];
6119
6340
  const ensureClaudeOrchLayout = (cwd) => migrateLegacyOrchLayout(cwd, {
6120
6341
  envGlobalPath: defaultTemplateConfig.envGlobalPath,
6121
6342
  envProjectPath: defaultTemplateConfig.envProjectPath,
@@ -6164,7 +6385,7 @@ const runClaudeAuthCommand = (cwd, accountPath, args, commandLabel, interactive)
6164
6385
  image: claudeImageName,
6165
6386
  hostPath: accountPath,
6166
6387
  containerPath: claudeContainerHomeDir,
6167
- env: buildClaudeAuthEnv(interactive),
6388
+ env: buildClaudeAuthEnv(),
6168
6389
  args,
6169
6390
  interactive
6170
6391
  }),
@@ -6172,13 +6393,13 @@ const runClaudeAuthCommand = (cwd, accountPath, args, commandLabel, interactive)
6172
6393
  (exitCode) => new CommandFailedError({ command: commandLabel, exitCode })
6173
6394
  );
6174
6395
  const runClaudeLogout = (cwd, accountPath) => runClaudeAuthCommand(cwd, accountPath, ["auth", "logout"], "claude auth logout", false);
6175
- const runClaudePingProbeExitCode = (cwd, accountPath) => runDockerAuthExitCode(
6396
+ const runClaudePingProbeExitCode = (cwd, accountPath, oauthToken) => runDockerAuthExitCode(
6176
6397
  buildDockerAuthSpec({
6177
6398
  cwd,
6178
6399
  image: claudeImageName,
6179
6400
  hostPath: accountPath,
6180
6401
  containerPath: claudeContainerHomeDir,
6181
- env: buildClaudeAuthEnv(false),
6402
+ env: buildClaudeAuthEnv(false, oauthToken),
6182
6403
  args: ["-p", "ping"],
6183
6404
  interactive: false
6184
6405
  })
@@ -6195,8 +6416,8 @@ const authClaudeLogin = (command) => {
6195
6416
  yield* _(fs.writeFileString(claudeOauthTokenPath(accountPath), `${token}
6196
6417
  `));
6197
6418
  yield* _(fs.chmod(claudeOauthTokenPath(accountPath), 384), Effect.orElseSucceed(() => void 0));
6198
- yield* _(syncClaudeCredentialsFile(fs, accountPath));
6199
- const probeExitCode = yield* _(runClaudePingProbeExitCode(cwd, accountPath));
6419
+ yield* _(resolveClaudeAuthMethod(fs, accountPath));
6420
+ const probeExitCode = yield* _(runClaudePingProbeExitCode(cwd, accountPath, token));
6200
6421
  if (probeExitCode !== 0) {
6201
6422
  yield* _(
6202
6423
  Effect.fail(
@@ -6212,20 +6433,17 @@ const authClaudeLogin = (command) => {
6212
6433
  );
6213
6434
  };
6214
6435
  const authClaudeStatus = (command) => withClaudeAuth(command, ({ accountLabel, accountPath, cwd, fs }) => Effect.gen(function* (_) {
6215
- yield* _(syncClaudeCredentialsFile(fs, accountPath));
6216
- const hasOauthToken = yield* _(hasNonEmptyOauthToken$1(fs, accountPath));
6217
- const hasCredentials = yield* _(isRegularFile(fs, claudeCredentialsPath(accountPath)));
6218
- if (!hasOauthToken && !hasCredentials) {
6436
+ const method = yield* _(resolveClaudeAuthMethod(fs, accountPath));
6437
+ if (method === "none") {
6219
6438
  yield* _(Effect.log(`Claude not connected (${accountLabel}).`));
6220
6439
  return;
6221
6440
  }
6222
- const probeExitCode = yield* _(runClaudePingProbeExitCode(cwd, accountPath));
6441
+ const oauthToken = method === "oauth-token" ? yield* _(readOauthToken(fs, accountPath)) : null;
6442
+ const probeExitCode = yield* _(runClaudePingProbeExitCode(cwd, accountPath, oauthToken));
6223
6443
  if (probeExitCode === 0) {
6224
- const method2 = hasCredentials ? "claude-ai-session" : "oauth-token";
6225
- yield* _(Effect.log(`Claude connected (${accountLabel}, ${method2}).`));
6444
+ yield* _(Effect.log(`Claude connected (${accountLabel}, ${method}).`));
6226
6445
  return;
6227
6446
  }
6228
- const method = hasCredentials ? "claude-ai-session" : "oauth-token";
6229
6447
  yield* _(
6230
6448
  Effect.logWarning(
6231
6449
  `Claude session exists but API probe failed (${accountLabel}, ${method}, exit=${probeExitCode}). Run 'docker-git auth claude login'.`