Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ This avoids `refresh_token` rotation issues that can happen when copying `auth.j
Disable sharing (per-project auth):
- Set `CODEX_SHARE_AUTH=0` in `.orch/env/project.env`.

## Claude Code Defaults

On container start, docker-git syncs Claude Code user settings under `$CLAUDE_CONFIG_DIR/settings.json`:
- `permissions.defaultMode = "bypassPermissions"` so local disposable containers behave like docker-git Codex containers (no permission prompts).
- Existing unrelated Claude settings are preserved.

## Playwright MCP (Chromium Sidecar)

Enable during create/clone:
Expand Down
14 changes: 13 additions & 1 deletion packages/app/tests/docker-git/entrypoint-auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,20 @@ describe("renderEntrypoint auth bridge", () => {
expect(entrypoint).toContain("CLAUDE_CONFIG_DIR=\"${CLAUDE_CONFIG_DIR:-$HOME/.claude}\"")
expect(entrypoint).toContain("docker_git_ensure_claude_cli()")
expect(entrypoint).toContain("claude cli.js not found under npm global root; skip shim restore")
expect(entrypoint).toContain("CLAUDE_PERMISSION_SETTINGS_FILE=\"$CLAUDE_CONFIG_DIR/settings.json\"")
expect(entrypoint).toContain("docker_git_sync_claude_permissions()")
expect(entrypoint).toContain(
"const currentPermissions = isRecord(settings.permissions) ? settings.permissions : {}"
)
expect(entrypoint).toContain("defaultMode: \"bypassPermissions\"")
expect(entrypoint).toContain("CLAUDE_TOKEN_FILE=\"$CLAUDE_CONFIG_DIR/.oauth-token\"")
expect(entrypoint).toContain("CLAUDE_CREDENTIALS_FILE=\"$CLAUDE_CONFIG_DIR/.credentials.json\"")
expect(entrypoint).toContain("if [[ -s \"$CLAUDE_CREDENTIALS_FILE\" ]]; then")
expect(entrypoint).toContain("CLAUDE_NESTED_CREDENTIALS_FILE=\"$CLAUDE_CONFIG_DIR/.claude/.credentials.json\"")
expect(entrypoint).toContain("docker_git_prepare_claude_auth_mode()")
expect(entrypoint).toContain(
"rm -f \"$CLAUDE_CREDENTIALS_FILE\" \"$CLAUDE_NESTED_CREDENTIALS_FILE\" \"$CLAUDE_HOME_DIR/.credentials.json\" || true"
)
expect(entrypoint).toContain("if [[ ! -s \"$CLAUDE_TOKEN_FILE\" ]]; then")
expect(entrypoint).toContain("CLAUDE_SETTINGS_FILE=\"${CLAUDE_HOME_JSON:-$CLAUDE_CONFIG_DIR/.claude.json}\"")
expect(entrypoint).toContain("nextServers.playwright = {")
expect(entrypoint).toContain("command: \"docker-git-playwright-mcp\"")
Expand Down
121 changes: 121 additions & 0 deletions packages/lib/src/core/templates-entrypoint/claude-extra-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import type { TemplateConfig } from "../domain.js"

const entrypointClaudeGlobalPromptTemplate = String
.raw`# Claude Code: managed global memory (CLAUDE.md is auto-loaded by Claude Code)
CLAUDE_GLOBAL_PROMPT_FILE="/home/__SSH_USER__/.claude/CLAUDE.md"
CLAUDE_AUTO_SYSTEM_PROMPT="${"$"}{CLAUDE_AUTO_SYSTEM_PROMPT:-1}"
CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: repository"
REPO_REF_VALUE="${"$"}{REPO_REF:-__REPO_REF_DEFAULT__}"
REPO_URL_VALUE="${"$"}{REPO_URL:-__REPO_URL_DEFAULT__}"

if [[ "$REPO_REF_VALUE" == issue-* ]]; then
ISSUE_ID_VALUE="$(printf "%s" "$REPO_REF_VALUE" | sed -E 's#^issue-##')"
ISSUE_URL_VALUE=""
if [[ "$REPO_URL_VALUE" == https://github.com/* ]]; then
ISSUE_REPO_VALUE="$(printf "%s" "$REPO_URL_VALUE" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')"
if [[ -n "$ISSUE_REPO_VALUE" ]]; then
ISSUE_URL_VALUE="https://github.com/$ISSUE_REPO_VALUE/issues/$ISSUE_ID_VALUE"
fi
fi
if [[ -n "$ISSUE_URL_VALUE" ]]; then
CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: issue #$ISSUE_ID_VALUE ($ISSUE_URL_VALUE)"
else
CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: issue #$ISSUE_ID_VALUE"
fi
elif [[ "$REPO_REF_VALUE" == refs/pull/*/head ]]; then
PR_ID_VALUE="$(printf "%s" "$REPO_REF_VALUE" | sed -nE 's#^refs/pull/([0-9]+)/head$#\1#p')"
PR_URL_VALUE=""
if [[ "$REPO_URL_VALUE" == https://github.com/* && -n "$PR_ID_VALUE" ]]; then
PR_REPO_VALUE="$(printf "%s" "$REPO_URL_VALUE" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')"
if [[ -n "$PR_REPO_VALUE" ]]; then
PR_URL_VALUE="https://github.com/$PR_REPO_VALUE/pull/$PR_ID_VALUE"
fi
fi
if [[ -n "$PR_ID_VALUE" && -n "$PR_URL_VALUE" ]]; then
CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: PR #$PR_ID_VALUE ($PR_URL_VALUE)"
elif [[ -n "$PR_ID_VALUE" ]]; then
CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: PR #$PR_ID_VALUE"
else
CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: pull request ($REPO_REF_VALUE)"
fi
fi

if [[ "$CLAUDE_AUTO_SYSTEM_PROMPT" == "1" ]]; then
mkdir -p "$(dirname "$CLAUDE_GLOBAL_PROMPT_FILE")"
chown 1000:1000 "$(dirname "$CLAUDE_GLOBAL_PROMPT_FILE")" 2>/dev/null || true
if [[ ! -f "$CLAUDE_GLOBAL_PROMPT_FILE" ]] || grep -q "^<!-- docker-git-managed:claude-md -->$" "$CLAUDE_GLOBAL_PROMPT_FILE"; then
cat <<EOF > "$CLAUDE_GLOBAL_PROMPT_FILE"
<!-- docker-git-managed:claude-md -->
Ты автономный агент, который имеет полностью все права управления контейнером. У тебя есть доступ к командам sudo, gh, codex, opencode, oh-my-opencode, claude, git, node, pnpm и всем остальным другим. Проекты с которыми идёт работа лежат по пути ~
Рабочая папка проекта (git clone): __TARGET_DIR__
Доступные workspace пути: __TARGET_DIR__
$CLAUDE_WORKSPACE_CONTEXT
Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__
Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе.
Если ты видишь файлы AGENTS.md или CLAUDE.md внутри проекта, ты обязан их читать и соблюдать инструкции.
<!-- /docker-git-managed:claude-md -->
EOF
chmod 0644 "$CLAUDE_GLOBAL_PROMPT_FILE" || true
chown 1000:1000 "$CLAUDE_GLOBAL_PROMPT_FILE" || true
fi
fi

export CLAUDE_AUTO_SYSTEM_PROMPT`

const escapeForDoubleQuotes = (value: string): string => {
const backslash = String.fromCodePoint(92)
const quote = String.fromCodePoint(34)
const escapedBackslash = `${backslash}${backslash}`
const escapedQuote = `${backslash}${quote}`
return value
.replaceAll(backslash, escapedBackslash)
.replaceAll(quote, escapedQuote)
}

export const renderClaudeGlobalPromptSetup = (config: TemplateConfig): string =>
entrypointClaudeGlobalPromptTemplate
.replaceAll("__TARGET_DIR__", config.targetDir)
.replaceAll("__SSH_USER__", config.sshUser)
.replaceAll("__REPO_REF_DEFAULT__", escapeForDoubleQuotes(config.repoRef))
.replaceAll("__REPO_URL_DEFAULT__", escapeForDoubleQuotes(config.repoUrl))

export const renderClaudeWrapperSetup = (): string =>
String.raw`CLAUDE_WRAPPER_BIN="/usr/local/bin/claude"
if command -v claude >/dev/null 2>&1; then
CURRENT_CLAUDE_BIN="$(command -v claude)"
CLAUDE_REAL_DIR="$(dirname "$CURRENT_CLAUDE_BIN")"
CLAUDE_REAL_BIN="$CLAUDE_REAL_DIR/.docker-git-claude-real"

# If a wrapper already exists but points to a missing real binary, recover from /usr/bin.
if [[ "$CURRENT_CLAUDE_BIN" == "$CLAUDE_WRAPPER_BIN" && ! -e "$CLAUDE_REAL_BIN" && -x "/usr/bin/claude" ]]; then
CURRENT_CLAUDE_BIN="/usr/bin/claude"
CLAUDE_REAL_DIR="/usr/bin"
CLAUDE_REAL_BIN="$CLAUDE_REAL_DIR/.docker-git-claude-real"
fi

# Keep the "real" binary in the same directory as the original command to preserve relative symlinks.
if [[ "$CURRENT_CLAUDE_BIN" != "$CLAUDE_REAL_BIN" && ! -e "$CLAUDE_REAL_BIN" ]]; then
mv "$CURRENT_CLAUDE_BIN" "$CLAUDE_REAL_BIN"
fi
if [[ -e "$CLAUDE_REAL_BIN" ]]; then
cat <<'EOF' > "$CLAUDE_WRAPPER_BIN"
#!/usr/bin/env bash
set -euo pipefail

CLAUDE_REAL_BIN="__CLAUDE_REAL_BIN__"
CLAUDE_CONFIG_DIR="${"$"}{CLAUDE_CONFIG_DIR:-$HOME/.claude}"
CLAUDE_TOKEN_FILE="$CLAUDE_CONFIG_DIR/.oauth-token"

if [[ -f "$CLAUDE_TOKEN_FILE" ]]; then
CLAUDE_CODE_OAUTH_TOKEN="$(tr -d '\r\n' < "$CLAUDE_TOKEN_FILE")"
export CLAUDE_CODE_OAUTH_TOKEN
else
unset CLAUDE_CODE_OAUTH_TOKEN || true
fi

exec "$CLAUDE_REAL_BIN" "$@"
EOF
sed -i "s#__CLAUDE_REAL_BIN__#$CLAUDE_REAL_BIN#g" "$CLAUDE_WRAPPER_BIN" || true
chmod 0755 "$CLAUDE_WRAPPER_BIN" || true
fi
fi`
Loading