@prover-coder-ai/docker-git 1.0.29 → 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.
- package/README.md +6 -0
- package/dist/src/docker-git/main.js +439 -218
- package/dist/src/docker-git/main.js.map +1 -1
- package/package.json +1 -1
|
@@ -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) =>
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(),
|
|
@@ -2618,8 +2795,11 @@ else
|
|
|
2618
2795
|
mkdir -p "$(dirname "$CODEX_CONFIG_FILE")" || true
|
|
2619
2796
|
cat <<'EOF' > "$CODEX_CONFIG_FILE"
|
|
2620
2797
|
# docker-git codex config
|
|
2621
|
-
model = "gpt-5.
|
|
2798
|
+
model = "gpt-5.4"
|
|
2799
|
+
model_context_window = 1050000
|
|
2800
|
+
model_auto_compact_token_limit = 945000
|
|
2622
2801
|
model_reasoning_effort = "xhigh"
|
|
2802
|
+
plan_mode_reasoning_effort = "xhigh"
|
|
2623
2803
|
personality = "pragmatic"
|
|
2624
2804
|
|
|
2625
2805
|
approval_policy = "never"
|
|
@@ -2749,10 +2929,7 @@ if [[ -s /etc/zsh/zshrc ]] && ! grep -q "zz-codex-resume.sh" /etc/zsh/zshrc 2>/d
|
|
|
2749
2929
|
fi`;
|
|
2750
2930
|
const escapeForDoubleQuotes = (value) => {
|
|
2751
2931
|
const backslash = String.fromCodePoint(92);
|
|
2752
|
-
|
|
2753
|
-
const escapedBackslash = `${backslash}${backslash}`;
|
|
2754
|
-
const escapedQuote = `${backslash}${quote}`;
|
|
2755
|
-
return value.replaceAll(backslash, escapedBackslash).replaceAll(quote, escapedQuote);
|
|
2932
|
+
return value.replaceAll(backslash, `${backslash}${backslash}`).replaceAll(String.fromCodePoint(34), `${backslash}${String.fromCodePoint(34)}`);
|
|
2756
2933
|
};
|
|
2757
2934
|
const renderEntrypointCodexResumeHint = (config) => entrypointCodexResumeHintTemplate.replaceAll("__REPO_REF_DEFAULT__", escapeForDoubleQuotes(config.repoRef)).replaceAll("__REPO_URL_DEFAULT__", escapeForDoubleQuotes(config.repoUrl));
|
|
2758
2935
|
const entrypointAgentsNoticeTemplate = String.raw`# Ensure global AGENTS.md exists for container context
|
|
@@ -4616,8 +4793,11 @@ const defaultEnvContents = "# docker-git env\n# KEY=value\n";
|
|
|
4616
4793
|
const codexConfigMarker = "# docker-git codex config";
|
|
4617
4794
|
const defaultCodexConfig = [
|
|
4618
4795
|
"# docker-git codex config",
|
|
4619
|
-
'model = "gpt-5.
|
|
4796
|
+
'model = "gpt-5.4"',
|
|
4797
|
+
"model_context_window = 1050000",
|
|
4798
|
+
"model_auto_compact_token_limit = 945000",
|
|
4620
4799
|
'model_reasoning_effort = "xhigh"',
|
|
4800
|
+
'plan_mode_reasoning_effort = "xhigh"',
|
|
4621
4801
|
'personality = "pragmatic"',
|
|
4622
4802
|
"",
|
|
4623
4803
|
'approval_policy = "never"',
|
|
@@ -4665,70 +4845,6 @@ const parseJsonRecord = (text) => Either.match(ParseResult.decodeUnknownEither(J
|
|
|
4665
4845
|
const hasClaudeOauthAccount = (record) => record !== null && typeof record["oauthAccount"] === "object" && record["oauthAccount"] !== null;
|
|
4666
4846
|
const hasClaudeCredentials = (record) => record !== null && typeof record["claudeAiOauth"] === "object" && record["claudeAiOauth"] !== null;
|
|
4667
4847
|
const isGithubTokenKey = (key) => key === "GITHUB_TOKEN" || key === "GH_TOKEN" || key.startsWith("GITHUB_TOKEN__");
|
|
4668
|
-
const syncGithubAuthKeys = (sourceText, targetText) => {
|
|
4669
|
-
const sourceTokenEntries = parseEnvEntries(sourceText).filter((entry) => isGithubTokenKey(entry.key));
|
|
4670
|
-
if (sourceTokenEntries.length === 0) {
|
|
4671
|
-
return targetText;
|
|
4672
|
-
}
|
|
4673
|
-
const targetTokenKeys = parseEnvEntries(targetText).filter((entry) => isGithubTokenKey(entry.key)).map((entry) => entry.key);
|
|
4674
|
-
let next = targetText;
|
|
4675
|
-
for (const key of targetTokenKeys) {
|
|
4676
|
-
next = removeEnvKey(next, key);
|
|
4677
|
-
}
|
|
4678
|
-
for (const entry of sourceTokenEntries) {
|
|
4679
|
-
next = upsertEnvKey(next, entry.key, entry.value);
|
|
4680
|
-
}
|
|
4681
|
-
return next;
|
|
4682
|
-
};
|
|
4683
|
-
const syncGithubTokenKeysInFile = (sourcePath, targetPath) => withFsPathContext(
|
|
4684
|
-
({ fs }) => Effect.gen(function* (_) {
|
|
4685
|
-
const sourceExists = yield* _(fs.exists(sourcePath));
|
|
4686
|
-
if (!sourceExists) {
|
|
4687
|
-
return;
|
|
4688
|
-
}
|
|
4689
|
-
const targetExists = yield* _(fs.exists(targetPath));
|
|
4690
|
-
if (!targetExists) {
|
|
4691
|
-
return;
|
|
4692
|
-
}
|
|
4693
|
-
const sourceInfo = yield* _(fs.stat(sourcePath));
|
|
4694
|
-
const targetInfo = yield* _(fs.stat(targetPath));
|
|
4695
|
-
if (sourceInfo.type !== "File" || targetInfo.type !== "File") {
|
|
4696
|
-
return;
|
|
4697
|
-
}
|
|
4698
|
-
const sourceText = yield* _(fs.readFileString(sourcePath));
|
|
4699
|
-
const targetText = yield* _(fs.readFileString(targetPath));
|
|
4700
|
-
const mergedText = syncGithubAuthKeys(sourceText, targetText);
|
|
4701
|
-
if (mergedText !== targetText) {
|
|
4702
|
-
yield* _(fs.writeFileString(targetPath, mergedText));
|
|
4703
|
-
yield* _(Effect.log(`Synced GitHub auth keys from ${sourcePath} to ${targetPath}`));
|
|
4704
|
-
}
|
|
4705
|
-
})
|
|
4706
|
-
);
|
|
4707
|
-
const copyFileIfNeeded = (sourcePath, targetPath) => withFsPathContext(
|
|
4708
|
-
({ fs, path }) => Effect.gen(function* (_) {
|
|
4709
|
-
const sourceExists = yield* _(fs.exists(sourcePath));
|
|
4710
|
-
if (!sourceExists) {
|
|
4711
|
-
return;
|
|
4712
|
-
}
|
|
4713
|
-
const sourceInfo = yield* _(fs.stat(sourcePath));
|
|
4714
|
-
if (sourceInfo.type !== "File") {
|
|
4715
|
-
return;
|
|
4716
|
-
}
|
|
4717
|
-
yield* _(fs.makeDirectory(path.dirname(targetPath), { recursive: true }));
|
|
4718
|
-
const targetExists = yield* _(fs.exists(targetPath));
|
|
4719
|
-
if (!targetExists) {
|
|
4720
|
-
yield* _(fs.copyFile(sourcePath, targetPath));
|
|
4721
|
-
yield* _(Effect.log(`Copied env file from ${sourcePath} to ${targetPath}`));
|
|
4722
|
-
return;
|
|
4723
|
-
}
|
|
4724
|
-
const sourceText = yield* _(fs.readFileString(sourcePath));
|
|
4725
|
-
const targetText = yield* _(fs.readFileString(targetPath));
|
|
4726
|
-
if (shouldCopyEnv(sourceText, targetText) === "copy") {
|
|
4727
|
-
yield* _(fs.writeFileString(targetPath, sourceText));
|
|
4728
|
-
yield* _(Effect.log(`Synced env file from ${sourcePath} to ${targetPath}`));
|
|
4729
|
-
}
|
|
4730
|
-
})
|
|
4731
|
-
);
|
|
4732
4848
|
const syncClaudeJsonFile = (fs, path, spec) => Effect.gen(function* (_) {
|
|
4733
4849
|
const sourceExists = yield* _(fs.exists(spec.sourcePath));
|
|
4734
4850
|
if (!sourceExists) {
|
|
@@ -4779,6 +4895,18 @@ const syncClaudeCredentialsJson = (fs, path, sourcePath, targetPath) => syncClau
|
|
|
4779
4895
|
seedLabel: "Claude credentials",
|
|
4780
4896
|
updateLabel: "Claude credentials"
|
|
4781
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
|
+
});
|
|
4782
4910
|
const ensureClaudeAuthSeedFromHome = (baseDir, claudeAuthPath) => withFsPathContext(
|
|
4783
4911
|
({ fs, path }) => Effect.gen(function* (_) {
|
|
4784
4912
|
const homeDir = (process.env["HOME"] ?? "").trim();
|
|
@@ -4790,10 +4918,78 @@ const ensureClaudeAuthSeedFromHome = (baseDir, claudeAuthPath) => withFsPathCont
|
|
|
4790
4918
|
const claudeRoot = resolvePathFromBase$1(path, baseDir, claudeAuthPath);
|
|
4791
4919
|
const targetAccountDir = path.join(claudeRoot, "default");
|
|
4792
4920
|
const targetClaudeJson = path.join(targetAccountDir, ".claude.json");
|
|
4921
|
+
const targetOauthToken = path.join(targetAccountDir, ".oauth-token");
|
|
4793
4922
|
const targetCredentials = path.join(targetAccountDir, ".credentials.json");
|
|
4923
|
+
const hasTargetOauthToken = yield* _(hasNonEmptyFile(fs, targetOauthToken));
|
|
4794
4924
|
yield* _(fs.makeDirectory(targetAccountDir, { recursive: true }));
|
|
4795
4925
|
yield* _(syncClaudeHomeJson(fs, path, sourceClaudeJson, targetClaudeJson));
|
|
4796
|
-
|
|
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
|
+
}
|
|
4797
4993
|
})
|
|
4798
4994
|
);
|
|
4799
4995
|
const ensureCodexConfigFile = (baseDir, codexAuthPath) => withFsPathContext(
|
|
@@ -6054,7 +6250,8 @@ const runClaudeOauthLoginWithPrompt = (cwd, accountPath, options) => {
|
|
|
6054
6250
|
return Effect.scoped(
|
|
6055
6251
|
Effect.gen(function* (_) {
|
|
6056
6252
|
const executor = yield* _(CommandExecutor.CommandExecutor);
|
|
6057
|
-
const
|
|
6253
|
+
const hostPath = yield* _(resolveDockerVolumeHostPath(cwd, accountPath));
|
|
6254
|
+
const spec = buildDockerSetupTokenSpec(cwd, hostPath, options.image, options.containerPath);
|
|
6058
6255
|
const proc = yield* _(startDockerProcess(executor, spec));
|
|
6059
6256
|
const tokenBox = { value: null };
|
|
6060
6257
|
const stdoutFiber = yield* _(Effect.forkScoped(pumpDockerOutput(proc.stdout, 1, tokenBox)));
|
|
@@ -6103,6 +6300,10 @@ const syncClaudeCredentialsFile = (fs, accountPath) => Effect.gen(function* (_)
|
|
|
6103
6300
|
yield* _(fs.chmod(nestedPath, 384), Effect.orElseSucceed(() => void 0));
|
|
6104
6301
|
}
|
|
6105
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
|
+
});
|
|
6106
6307
|
const hasNonEmptyOauthToken$1 = (fs, accountPath) => Effect.gen(function* (_) {
|
|
6107
6308
|
const tokenPath = claudeOauthTokenPath(accountPath);
|
|
6108
6309
|
const hasToken = yield* _(isRegularFile(fs, tokenPath));
|
|
@@ -6112,7 +6313,30 @@ const hasNonEmptyOauthToken$1 = (fs, accountPath) => Effect.gen(function* (_) {
|
|
|
6112
6313
|
const tokenText = yield* _(fs.readFileString(tokenPath), Effect.orElseSucceed(() => ""));
|
|
6113
6314
|
return tokenText.trim().length > 0;
|
|
6114
6315
|
});
|
|
6115
|
-
const
|
|
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
|
+
];
|
|
6116
6340
|
const ensureClaudeOrchLayout = (cwd) => migrateLegacyOrchLayout(cwd, {
|
|
6117
6341
|
envGlobalPath: defaultTemplateConfig.envGlobalPath,
|
|
6118
6342
|
envProjectPath: defaultTemplateConfig.envProjectPath,
|
|
@@ -6161,7 +6385,7 @@ const runClaudeAuthCommand = (cwd, accountPath, args, commandLabel, interactive)
|
|
|
6161
6385
|
image: claudeImageName,
|
|
6162
6386
|
hostPath: accountPath,
|
|
6163
6387
|
containerPath: claudeContainerHomeDir,
|
|
6164
|
-
env: buildClaudeAuthEnv(
|
|
6388
|
+
env: buildClaudeAuthEnv(),
|
|
6165
6389
|
args,
|
|
6166
6390
|
interactive
|
|
6167
6391
|
}),
|
|
@@ -6169,13 +6393,13 @@ const runClaudeAuthCommand = (cwd, accountPath, args, commandLabel, interactive)
|
|
|
6169
6393
|
(exitCode) => new CommandFailedError({ command: commandLabel, exitCode })
|
|
6170
6394
|
);
|
|
6171
6395
|
const runClaudeLogout = (cwd, accountPath) => runClaudeAuthCommand(cwd, accountPath, ["auth", "logout"], "claude auth logout", false);
|
|
6172
|
-
const runClaudePingProbeExitCode = (cwd, accountPath) => runDockerAuthExitCode(
|
|
6396
|
+
const runClaudePingProbeExitCode = (cwd, accountPath, oauthToken) => runDockerAuthExitCode(
|
|
6173
6397
|
buildDockerAuthSpec({
|
|
6174
6398
|
cwd,
|
|
6175
6399
|
image: claudeImageName,
|
|
6176
6400
|
hostPath: accountPath,
|
|
6177
6401
|
containerPath: claudeContainerHomeDir,
|
|
6178
|
-
env: buildClaudeAuthEnv(false),
|
|
6402
|
+
env: buildClaudeAuthEnv(false, oauthToken),
|
|
6179
6403
|
args: ["-p", "ping"],
|
|
6180
6404
|
interactive: false
|
|
6181
6405
|
})
|
|
@@ -6192,8 +6416,8 @@ const authClaudeLogin = (command) => {
|
|
|
6192
6416
|
yield* _(fs.writeFileString(claudeOauthTokenPath(accountPath), `${token}
|
|
6193
6417
|
`));
|
|
6194
6418
|
yield* _(fs.chmod(claudeOauthTokenPath(accountPath), 384), Effect.orElseSucceed(() => void 0));
|
|
6195
|
-
yield* _(
|
|
6196
|
-
const probeExitCode = yield* _(runClaudePingProbeExitCode(cwd, accountPath));
|
|
6419
|
+
yield* _(resolveClaudeAuthMethod(fs, accountPath));
|
|
6420
|
+
const probeExitCode = yield* _(runClaudePingProbeExitCode(cwd, accountPath, token));
|
|
6197
6421
|
if (probeExitCode !== 0) {
|
|
6198
6422
|
yield* _(
|
|
6199
6423
|
Effect.fail(
|
|
@@ -6209,20 +6433,17 @@ const authClaudeLogin = (command) => {
|
|
|
6209
6433
|
);
|
|
6210
6434
|
};
|
|
6211
6435
|
const authClaudeStatus = (command) => withClaudeAuth(command, ({ accountLabel, accountPath, cwd, fs }) => Effect.gen(function* (_) {
|
|
6212
|
-
yield* _(
|
|
6213
|
-
|
|
6214
|
-
const hasCredentials = yield* _(isRegularFile(fs, claudeCredentialsPath(accountPath)));
|
|
6215
|
-
if (!hasOauthToken && !hasCredentials) {
|
|
6436
|
+
const method = yield* _(resolveClaudeAuthMethod(fs, accountPath));
|
|
6437
|
+
if (method === "none") {
|
|
6216
6438
|
yield* _(Effect.log(`Claude not connected (${accountLabel}).`));
|
|
6217
6439
|
return;
|
|
6218
6440
|
}
|
|
6219
|
-
const
|
|
6441
|
+
const oauthToken = method === "oauth-token" ? yield* _(readOauthToken(fs, accountPath)) : null;
|
|
6442
|
+
const probeExitCode = yield* _(runClaudePingProbeExitCode(cwd, accountPath, oauthToken));
|
|
6220
6443
|
if (probeExitCode === 0) {
|
|
6221
|
-
|
|
6222
|
-
yield* _(Effect.log(`Claude connected (${accountLabel}, ${method2}).`));
|
|
6444
|
+
yield* _(Effect.log(`Claude connected (${accountLabel}, ${method}).`));
|
|
6223
6445
|
return;
|
|
6224
6446
|
}
|
|
6225
|
-
const method = hasCredentials ? "claude-ai-session" : "oauth-token";
|
|
6226
6447
|
yield* _(
|
|
6227
6448
|
Effect.logWarning(
|
|
6228
6449
|
`Claude session exists but API probe failed (${accountLabel}, ${method}, exit=${probeExitCode}). Run 'docker-git auth claude login'.`
|