|
| 1 | +import type { TemplateConfig } from "../domain.js" |
| 2 | + |
| 3 | +const entrypointClaudeGlobalPromptTemplate = String |
| 4 | + .raw`# Claude Code: managed global memory (CLAUDE.md is auto-loaded by Claude Code) |
| 5 | +CLAUDE_GLOBAL_PROMPT_FILE="/home/__SSH_USER__/.claude/CLAUDE.md" |
| 6 | +CLAUDE_AUTO_SYSTEM_PROMPT="${"$"}{CLAUDE_AUTO_SYSTEM_PROMPT:-1}" |
| 7 | +CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: repository" |
| 8 | +REPO_REF_VALUE="${"$"}{REPO_REF:-__REPO_REF_DEFAULT__}" |
| 9 | +REPO_URL_VALUE="${"$"}{REPO_URL:-__REPO_URL_DEFAULT__}" |
| 10 | +
|
| 11 | +if [[ "$REPO_REF_VALUE" == issue-* ]]; then |
| 12 | + ISSUE_ID_VALUE="$(printf "%s" "$REPO_REF_VALUE" | sed -E 's#^issue-##')" |
| 13 | + ISSUE_URL_VALUE="" |
| 14 | + if [[ "$REPO_URL_VALUE" == https://github.com/* ]]; then |
| 15 | + ISSUE_REPO_VALUE="$(printf "%s" "$REPO_URL_VALUE" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')" |
| 16 | + if [[ -n "$ISSUE_REPO_VALUE" ]]; then |
| 17 | + ISSUE_URL_VALUE="https://github.com/$ISSUE_REPO_VALUE/issues/$ISSUE_ID_VALUE" |
| 18 | + fi |
| 19 | + fi |
| 20 | + if [[ -n "$ISSUE_URL_VALUE" ]]; then |
| 21 | + CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: issue #$ISSUE_ID_VALUE ($ISSUE_URL_VALUE)" |
| 22 | + else |
| 23 | + CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: issue #$ISSUE_ID_VALUE" |
| 24 | + fi |
| 25 | +elif [[ "$REPO_REF_VALUE" == refs/pull/*/head ]]; then |
| 26 | + PR_ID_VALUE="$(printf "%s" "$REPO_REF_VALUE" | sed -nE 's#^refs/pull/([0-9]+)/head$#\1#p')" |
| 27 | + PR_URL_VALUE="" |
| 28 | + if [[ "$REPO_URL_VALUE" == https://github.com/* && -n "$PR_ID_VALUE" ]]; then |
| 29 | + PR_REPO_VALUE="$(printf "%s" "$REPO_URL_VALUE" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')" |
| 30 | + if [[ -n "$PR_REPO_VALUE" ]]; then |
| 31 | + PR_URL_VALUE="https://github.com/$PR_REPO_VALUE/pull/$PR_ID_VALUE" |
| 32 | + fi |
| 33 | + fi |
| 34 | + if [[ -n "$PR_ID_VALUE" && -n "$PR_URL_VALUE" ]]; then |
| 35 | + CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: PR #$PR_ID_VALUE ($PR_URL_VALUE)" |
| 36 | + elif [[ -n "$PR_ID_VALUE" ]]; then |
| 37 | + CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: PR #$PR_ID_VALUE" |
| 38 | + else |
| 39 | + CLAUDE_WORKSPACE_CONTEXT="Контекст workspace: pull request ($REPO_REF_VALUE)" |
| 40 | + fi |
| 41 | +fi |
| 42 | +
|
| 43 | +if [[ "$CLAUDE_AUTO_SYSTEM_PROMPT" == "1" ]]; then |
| 44 | + mkdir -p "$(dirname "$CLAUDE_GLOBAL_PROMPT_FILE")" |
| 45 | + chown 1000:1000 "$(dirname "$CLAUDE_GLOBAL_PROMPT_FILE")" 2>/dev/null || true |
| 46 | + if [[ ! -f "$CLAUDE_GLOBAL_PROMPT_FILE" ]] || grep -q "^<!-- docker-git-managed:claude-md -->$" "$CLAUDE_GLOBAL_PROMPT_FILE"; then |
| 47 | + cat <<EOF > "$CLAUDE_GLOBAL_PROMPT_FILE" |
| 48 | +<!-- docker-git-managed:claude-md --> |
| 49 | +Ты автономный агент, который имеет полностью все права управления контейнером. У тебя есть доступ к командам sudo, gh, codex, opencode, oh-my-opencode, claude, git, node, pnpm и всем остальным другим. Проекты с которыми идёт работа лежат по пути ~ |
| 50 | +Рабочая папка проекта (git clone): __TARGET_DIR__ |
| 51 | +Доступные workspace пути: __TARGET_DIR__ |
| 52 | +$CLAUDE_WORKSPACE_CONTEXT |
| 53 | +Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__ |
| 54 | +Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе. |
| 55 | +Если ты видишь файлы AGENTS.md или CLAUDE.md внутри проекта, ты обязан их читать и соблюдать инструкции. |
| 56 | +<!-- /docker-git-managed:claude-md --> |
| 57 | +EOF |
| 58 | + chmod 0644 "$CLAUDE_GLOBAL_PROMPT_FILE" || true |
| 59 | + chown 1000:1000 "$CLAUDE_GLOBAL_PROMPT_FILE" || true |
| 60 | + fi |
| 61 | +fi |
| 62 | +
|
| 63 | +export CLAUDE_AUTO_SYSTEM_PROMPT` |
| 64 | + |
| 65 | +const escapeForDoubleQuotes = (value: string): string => { |
| 66 | + const backslash = String.fromCodePoint(92) |
| 67 | + const quote = String.fromCodePoint(34) |
| 68 | + const escapedBackslash = `${backslash}${backslash}` |
| 69 | + const escapedQuote = `${backslash}${quote}` |
| 70 | + return value |
| 71 | + .replaceAll(backslash, escapedBackslash) |
| 72 | + .replaceAll(quote, escapedQuote) |
| 73 | +} |
| 74 | + |
| 75 | +export const renderClaudeGlobalPromptSetup = (config: TemplateConfig): string => |
| 76 | + entrypointClaudeGlobalPromptTemplate |
| 77 | + .replaceAll("__TARGET_DIR__", config.targetDir) |
| 78 | + .replaceAll("__SSH_USER__", config.sshUser) |
| 79 | + .replaceAll("__REPO_REF_DEFAULT__", escapeForDoubleQuotes(config.repoRef)) |
| 80 | + .replaceAll("__REPO_URL_DEFAULT__", escapeForDoubleQuotes(config.repoUrl)) |
| 81 | + |
| 82 | +export const renderClaudeWrapperSetup = (): string => |
| 83 | + String.raw`CLAUDE_WRAPPER_BIN="/usr/local/bin/claude" |
| 84 | +if command -v claude >/dev/null 2>&1; then |
| 85 | + CURRENT_CLAUDE_BIN="$(command -v claude)" |
| 86 | + CLAUDE_REAL_DIR="$(dirname "$CURRENT_CLAUDE_BIN")" |
| 87 | + CLAUDE_REAL_BIN="$CLAUDE_REAL_DIR/.docker-git-claude-real" |
| 88 | +
|
| 89 | + # If a wrapper already exists but points to a missing real binary, recover from /usr/bin. |
| 90 | + if [[ "$CURRENT_CLAUDE_BIN" == "$CLAUDE_WRAPPER_BIN" && ! -e "$CLAUDE_REAL_BIN" && -x "/usr/bin/claude" ]]; then |
| 91 | + CURRENT_CLAUDE_BIN="/usr/bin/claude" |
| 92 | + CLAUDE_REAL_DIR="/usr/bin" |
| 93 | + CLAUDE_REAL_BIN="$CLAUDE_REAL_DIR/.docker-git-claude-real" |
| 94 | + fi |
| 95 | +
|
| 96 | + # Keep the "real" binary in the same directory as the original command to preserve relative symlinks. |
| 97 | + if [[ "$CURRENT_CLAUDE_BIN" != "$CLAUDE_REAL_BIN" && ! -e "$CLAUDE_REAL_BIN" ]]; then |
| 98 | + mv "$CURRENT_CLAUDE_BIN" "$CLAUDE_REAL_BIN" |
| 99 | + fi |
| 100 | + if [[ -e "$CLAUDE_REAL_BIN" ]]; then |
| 101 | + cat <<'EOF' > "$CLAUDE_WRAPPER_BIN" |
| 102 | +#!/usr/bin/env bash |
| 103 | +set -euo pipefail |
| 104 | +
|
| 105 | +CLAUDE_REAL_BIN="__CLAUDE_REAL_BIN__" |
| 106 | +CLAUDE_CONFIG_DIR="${"$"}{CLAUDE_CONFIG_DIR:-$HOME/.claude}" |
| 107 | +CLAUDE_TOKEN_FILE="$CLAUDE_CONFIG_DIR/.oauth-token" |
| 108 | +
|
| 109 | +if [[ -f "$CLAUDE_TOKEN_FILE" ]]; then |
| 110 | + CLAUDE_CODE_OAUTH_TOKEN="$(tr -d '\r\n' < "$CLAUDE_TOKEN_FILE")" |
| 111 | + export CLAUDE_CODE_OAUTH_TOKEN |
| 112 | +else |
| 113 | + unset CLAUDE_CODE_OAUTH_TOKEN || true |
| 114 | +fi |
| 115 | +
|
| 116 | +exec "$CLAUDE_REAL_BIN" "$@" |
| 117 | +EOF |
| 118 | + sed -i "s#__CLAUDE_REAL_BIN__#$CLAUDE_REAL_BIN#g" "$CLAUDE_WRAPPER_BIN" || true |
| 119 | + chmod 0755 "$CLAUDE_WRAPPER_BIN" || true |
| 120 | + fi |
| 121 | +fi` |
0 commit comments