@prover-coder-ai/docker-git 1.0.44 → 1.0.46
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/dist/src/docker-git/main.js +399 -204
- package/dist/src/docker-git/main.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|
3
|
-
import { Data, Duration, Effect, Either, Fiber, Match, Option, Schedule, pipe } from "effect";
|
|
3
|
+
import { Data, Deferred, Duration, Effect, Either, Fiber, Match, Option, Schedule, pipe } from "effect";
|
|
4
4
|
import * as FileSystem from "@effect/platform/FileSystem";
|
|
5
5
|
import * as Path from "@effect/platform/Path";
|
|
6
6
|
import * as Command from "@effect/platform/Command";
|
|
@@ -286,7 +286,7 @@ var runCommandWithExitCodes = (spec, okExitCodes, onFailure) => Effect.gen(funct
|
|
|
286
286
|
const exitCode = yield* _(Command.exitCode(buildCommand(spec, "inherit", "inherit", "inherit")));
|
|
287
287
|
yield* _(ensureExitCode(Number(exitCode), okExitCodes, onFailure));
|
|
288
288
|
});
|
|
289
|
-
var runCommandExitCode = (spec) => Effect.map(Command.exitCode(buildCommand(spec, "pipe", "pipe", "
|
|
289
|
+
var runCommandExitCode = (spec) => Effect.map(Command.exitCode(buildCommand(spec, "pipe", "pipe", "pipe")), Number);
|
|
290
290
|
var collectUint8Array$1 = (chunks) => Chunk.reduce(chunks, new Uint8Array(), (acc, curr) => {
|
|
291
291
|
const next = new Uint8Array(acc.length + curr.length);
|
|
292
292
|
next.set(acc);
|
|
@@ -1394,7 +1394,7 @@ var readProjectConfig = (baseDir) => Effect.gen(function* (_) {
|
|
|
1394
1394
|
//#endregion
|
|
1395
1395
|
//#region ../lib/src/usecases/docker-git-config-search.ts
|
|
1396
1396
|
var isDockerGitConfig = (entry) => entry.endsWith("docker-git.json");
|
|
1397
|
-
var shouldSkipDir = (entry) => entry === ".git" || entry === ".orch" || entry === ".docker-git" || entry === ".cache";
|
|
1397
|
+
var shouldSkipDir = (entry) => entry === ".git" || entry === ".orch" || entry === ".docker-git" || entry === ".cache" || entry === "node_modules";
|
|
1398
1398
|
var isNotFoundStatError = (error) => error._tag === "SystemError" && error.reason === "NotFound";
|
|
1399
1399
|
var processDockerGitEntry = (fs, path, dir, entry, state) => Effect.gen(function* (_) {
|
|
1400
1400
|
if (shouldSkipDir(entry)) return;
|
|
@@ -1620,6 +1620,7 @@ var resolveTemplateResourceLimits = (template) => Effect.succeed(withDefaultReso
|
|
|
1620
1620
|
var successExitCode = Number(ExitCode(0));
|
|
1621
1621
|
var gitBaseEnv$1 = {
|
|
1622
1622
|
GIT_TERMINAL_PROMPT: "0",
|
|
1623
|
+
GIT_SSH_COMMAND: "ssh -o BatchMode=yes",
|
|
1623
1624
|
GIT_AUTHOR_NAME: "docker-git",
|
|
1624
1625
|
GIT_AUTHOR_EMAIL: "docker-git@users.noreply.github.com",
|
|
1625
1626
|
GIT_COMMITTER_NAME: "docker-git",
|
|
@@ -2178,6 +2179,7 @@ elif [[ "$TARGET_DIR" == "~/"* ]]; then
|
|
|
2178
2179
|
fi
|
|
2179
2180
|
CLAUDE_AUTH_LABEL="\${CLAUDE_AUTH_LABEL:-}"
|
|
2180
2181
|
CODEX_AUTH_LABEL="\${CODEX_AUTH_LABEL:-}"
|
|
2182
|
+
GEMINI_AUTH_LABEL="\${GEMINI_AUTH_LABEL:-}"
|
|
2181
2183
|
GIT_AUTH_USER="\${GIT_AUTH_USER:-\${GITHUB_USER:-x-access-token}}"
|
|
2182
2184
|
GIT_AUTH_TOKEN="\${GIT_AUTH_TOKEN:-\${GITHUB_TOKEN:-\${GH_TOKEN:-}}}"
|
|
2183
2185
|
GH_TOKEN="\${GH_TOKEN:-\${GIT_AUTH_TOKEN:-}}"
|
|
@@ -2980,8 +2982,8 @@ var renderEntrypointAgentsNotice = (config) => entrypointAgentsNoticeTemplate.re
|
|
|
2980
2982
|
//#endregion
|
|
2981
2983
|
//#region ../lib/src/core/templates-entrypoint/gemini.ts
|
|
2982
2984
|
var geminiAuthRootContainerPath = (sshUser) => `/home/${sshUser}/.docker-git/.orch/auth/gemini`;
|
|
2983
|
-
var geminiAuthConfigTemplate = String.raw`# Gemini CLI: expose
|
|
2984
|
-
GEMINI_LABEL_RAW="$
|
|
2985
|
+
var geminiAuthConfigTemplate = String.raw`# Gemini CLI: expose GEMINI_HOME for sessions (OAuth cache lives under ~/.docker-git/.orch/auth/gemini)
|
|
2986
|
+
GEMINI_LABEL_RAW="$GEMINI_AUTH_LABEL"
|
|
2985
2987
|
if [[ -z "$GEMINI_LABEL_RAW" ]]; then
|
|
2986
2988
|
GEMINI_LABEL_RAW="default"
|
|
2987
2989
|
fi
|
|
@@ -2994,29 +2996,16 @@ if [[ -z "$GEMINI_LABEL_NORM" ]]; then
|
|
|
2994
2996
|
fi
|
|
2995
2997
|
|
|
2996
2998
|
GEMINI_AUTH_ROOT="__GEMINI_AUTH_ROOT__"
|
|
2997
|
-
|
|
2999
|
+
export GEMINI_CONFIG_DIR="$GEMINI_AUTH_ROOT/$GEMINI_LABEL_NORM"
|
|
2998
3000
|
|
|
2999
|
-
|
|
3000
|
-
if [[ "$GEMINI_LABEL_NORM" == "default" ]]; then
|
|
3001
|
-
GEMINI_ROOT_ENV_FILE="$GEMINI_AUTH_ROOT/.env"
|
|
3002
|
-
if [[ -f "$GEMINI_ROOT_ENV_FILE" ]]; then
|
|
3003
|
-
GEMINI_AUTH_DIR="$GEMINI_AUTH_ROOT"
|
|
3004
|
-
fi
|
|
3005
|
-
fi
|
|
3006
|
-
|
|
3007
|
-
mkdir -p "$GEMINI_AUTH_DIR" || true
|
|
3001
|
+
mkdir -p "$GEMINI_CONFIG_DIR" || true
|
|
3008
3002
|
GEMINI_HOME_DIR="__GEMINI_HOME_DIR__"
|
|
3009
3003
|
mkdir -p "$GEMINI_HOME_DIR" || true
|
|
3010
3004
|
|
|
3011
|
-
GEMINI_API_KEY_FILE="$GEMINI_AUTH_DIR/.api-key"
|
|
3012
|
-
GEMINI_ENV_FILE="$GEMINI_AUTH_DIR/.env"
|
|
3013
|
-
GEMINI_HOME_ENV_FILE="$GEMINI_HOME_DIR/.env"
|
|
3014
|
-
|
|
3015
3005
|
docker_git_link_gemini_file() {
|
|
3016
3006
|
local source_path="$1"
|
|
3017
3007
|
local link_path="$2"
|
|
3018
3008
|
|
|
3019
|
-
# Preserve user-created regular files and seed config dir once.
|
|
3020
3009
|
if [[ -e "$link_path" && ! -L "$link_path" ]]; then
|
|
3021
3010
|
if [[ -f "$link_path" && ! -e "$source_path" ]]; then
|
|
3022
3011
|
cp "$link_path" "$source_path" || true
|
|
@@ -3028,91 +3017,170 @@ docker_git_link_gemini_file() {
|
|
|
3028
3017
|
ln -sfn "$source_path" "$link_path" || true
|
|
3029
3018
|
}
|
|
3030
3019
|
|
|
3031
|
-
# Link
|
|
3032
|
-
docker_git_link_gemini_file "$
|
|
3033
|
-
|
|
3034
|
-
docker_git_refresh_gemini_api_key() {
|
|
3035
|
-
local api_key=""
|
|
3036
|
-
# Try to read from dedicated API key file first
|
|
3037
|
-
if [[ -f "$GEMINI_API_KEY_FILE" ]]; then
|
|
3038
|
-
api_key="$(tr -d '\r\n' < "$GEMINI_API_KEY_FILE")"
|
|
3039
|
-
fi
|
|
3040
|
-
# Fall back to .env file
|
|
3041
|
-
if [[ -z "$api_key" && -f "$GEMINI_ENV_FILE" ]]; then
|
|
3042
|
-
api_key="$(grep -E '^GEMINI_API_KEY=' "$GEMINI_ENV_FILE" 2>/dev/null | head -1 | cut -d'=' -f2- | tr -d '\r\n' | sed "s/^['\"]//;s/['\"]$//")"
|
|
3043
|
-
fi
|
|
3044
|
-
if [[ -n "$api_key" ]]; then
|
|
3045
|
-
export GEMINI_API_KEY="$api_key"
|
|
3046
|
-
else
|
|
3047
|
-
unset GEMINI_API_KEY || true
|
|
3048
|
-
fi
|
|
3049
|
-
}
|
|
3020
|
+
# Link .api-key and .env from central auth storage to container home
|
|
3021
|
+
docker_git_link_gemini_file "$GEMINI_CONFIG_DIR/.api-key" "$GEMINI_HOME_DIR/.api-key"
|
|
3022
|
+
docker_git_link_gemini_file "$GEMINI_CONFIG_DIR/.env" "$GEMINI_HOME_DIR/.env"
|
|
3050
3023
|
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
if
|
|
3056
|
-
|
|
3024
|
+
# Ensure gemini YOLO wrapper exists
|
|
3025
|
+
GEMINI_REAL_BIN="$(command -v gemini || echo "/usr/local/bin/gemini")"
|
|
3026
|
+
GEMINI_WRAPPER_BIN="/usr/local/bin/gemini-wrapper"
|
|
3027
|
+
if [[ -f "$GEMINI_REAL_BIN" && "$GEMINI_REAL_BIN" != "$GEMINI_WRAPPER_BIN" ]]; then
|
|
3028
|
+
if [[ ! -f "$GEMINI_WRAPPER_BIN" ]]; then
|
|
3029
|
+
cat <<'EOF' > "$GEMINI_WRAPPER_BIN"
|
|
3030
|
+
#!/usr/bin/env bash
|
|
3031
|
+
GEMINI_ORIGINAL_BIN="__GEMINI_REAL_BIN__"
|
|
3032
|
+
exec "$GEMINI_ORIGINAL_BIN" --yolo "$@"
|
|
3033
|
+
EOF
|
|
3034
|
+
sed -i "s#__GEMINI_REAL_BIN__#$GEMINI_REAL_BIN#g" "$GEMINI_WRAPPER_BIN" || true
|
|
3035
|
+
chmod 0755 "$GEMINI_WRAPPER_BIN" || true
|
|
3036
|
+
# Create an alias or symlink if needed, but here we just ensure it exists
|
|
3057
3037
|
fi
|
|
3038
|
+
fi
|
|
3058
3039
|
|
|
3059
|
-
|
|
3060
|
-
|
|
3040
|
+
# Special case for .gemini folder: we want the folder itself to be the link if it doesn't exist
|
|
3041
|
+
# or its content to be linked if we want to manage it.
|
|
3042
|
+
if [[ -d "$GEMINI_CONFIG_DIR/.gemini" ]]; then
|
|
3043
|
+
if [[ -L "$GEMINI_HOME_DIR" ]]; then
|
|
3044
|
+
rm -f "$GEMINI_HOME_DIR"
|
|
3045
|
+
elif [[ -d "$GEMINI_HOME_DIR" ]]; then
|
|
3046
|
+
# If it's a real directory, move it aside if it's empty or just has our managed files
|
|
3047
|
+
mv "$GEMINI_HOME_DIR" "$GEMINI_HOME_DIR.bak-$(date +%s)" || true
|
|
3061
3048
|
fi
|
|
3049
|
+
ln -sfn "$GEMINI_CONFIG_DIR/.gemini" "$GEMINI_HOME_DIR"
|
|
3050
|
+
fi
|
|
3062
3051
|
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
if [[ -
|
|
3066
|
-
|
|
3067
|
-
|
|
3052
|
+
docker_git_refresh_gemini_env() {
|
|
3053
|
+
# If .api-key exists, export it as GEMINI_API_KEY
|
|
3054
|
+
if [[ -f "$GEMINI_HOME_DIR/.api-key" ]]; then
|
|
3055
|
+
export GEMINI_API_KEY="$(cat "$GEMINI_HOME_DIR/.api-key" | tr -d '\r\n')"
|
|
3056
|
+
elif [[ -f "$GEMINI_HOME_DIR/.env" ]]; then
|
|
3057
|
+
# Parse GEMINI_API_KEY from .env
|
|
3058
|
+
API_KEY="$(grep "^GEMINI_API_KEY=" "$GEMINI_HOME_DIR/.env" | cut -d'=' -f2- | sed "s/^['\"]//;s/['\"]$//")"
|
|
3059
|
+
if [[ -n "$API_KEY" ]]; then
|
|
3060
|
+
export GEMINI_API_KEY="$API_KEY"
|
|
3061
|
+
fi
|
|
3068
3062
|
fi
|
|
3063
|
+
}
|
|
3069
3064
|
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3065
|
+
docker_git_refresh_gemini_env`;
|
|
3066
|
+
var renderGeminiAuthConfig = (config) => geminiAuthConfigTemplate.replaceAll("__GEMINI_AUTH_ROOT__", geminiAuthRootContainerPath(config.sshUser)).replaceAll("__GEMINI_HOME_DIR__", config.geminiHome);
|
|
3067
|
+
var renderGeminiPermissionSettingsConfig = (config) => String.raw`# Gemini CLI: keep trust settings in sync with docker-git defaults
|
|
3068
|
+
GEMINI_SETTINGS_DIR="${config.geminiHome}"
|
|
3069
|
+
GEMINI_TRUST_SETTINGS_FILE="$GEMINI_SETTINGS_DIR/trustedFolders.json"
|
|
3070
|
+
GEMINI_CONFIG_SETTINGS_FILE="$GEMINI_SETTINGS_DIR/settings.json"
|
|
3074
3071
|
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
exit 127
|
|
3078
|
-
fi
|
|
3072
|
+
# Wait for symlink to be established by the auth config step
|
|
3073
|
+
mkdir -p "$GEMINI_SETTINGS_DIR" || true
|
|
3079
3074
|
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3075
|
+
# Disable folder trust prompt and enable auto-approval in settings.json
|
|
3076
|
+
if [[ ! -f "$GEMINI_CONFIG_SETTINGS_FILE" ]]; then
|
|
3077
|
+
cat <<'EOF' > "$GEMINI_CONFIG_SETTINGS_FILE"
|
|
3078
|
+
{
|
|
3079
|
+
"security": {
|
|
3080
|
+
"folderTrust": {
|
|
3081
|
+
"enabled": false
|
|
3082
|
+
},
|
|
3083
|
+
"approvalPolicy": "never"
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
EOF
|
|
3085
3087
|
fi
|
|
3086
3088
|
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3089
|
+
# Pre-trust important directories in trustedFolders.json
|
|
3090
|
+
# Use flat mapping as required by recent Gemini CLI versions
|
|
3091
|
+
cat <<'EOF' > "$GEMINI_TRUST_SETTINGS_FILE"
|
|
3092
|
+
{
|
|
3093
|
+
"/": "TRUST_FOLDER",
|
|
3094
|
+
"${config.geminiHome}": "TRUST_FOLDER",
|
|
3095
|
+
"${config.targetDir}": "TRUST_FOLDER"
|
|
3091
3096
|
}
|
|
3097
|
+
EOF
|
|
3092
3098
|
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3099
|
+
chown -R 1000:1000 "$GEMINI_SETTINGS_DIR" || true
|
|
3100
|
+
chmod 0600 "$GEMINI_TRUST_SETTINGS_FILE" "$GEMINI_CONFIG_SETTINGS_FILE" 2>/dev/null || true`;
|
|
3101
|
+
var renderGeminiSudoConfig = (config) => String.raw`# Gemini CLI: allow passwordless sudo for agent tasks
|
|
3102
|
+
if [[ -d /etc/sudoers.d ]]; then
|
|
3103
|
+
echo "${config.sshUser} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/gemini-agent
|
|
3104
|
+
chmod 0440 /etc/sudoers.d/gemini-agent
|
|
3105
|
+
fi`;
|
|
3106
|
+
var renderGeminiMcpPlaywrightConfig = (_config) => String.raw`# Gemini CLI: keep Playwright MCP config in sync (TODO: Gemini CLI MCP integration format)
|
|
3107
|
+
# For now, Gemini CLI uses MCP via ~/.gemini/settings.json or command line.
|
|
3108
|
+
# We'll ensure it has the same Playwright capability as Claude/Codex once format is confirmed.`;
|
|
3109
|
+
var renderGeminiProfileSetup = (config) => String.raw`GEMINI_PROFILE="/etc/profile.d/gemini-config.sh"
|
|
3110
|
+
printf "export GEMINI_AUTH_LABEL=%q\n" "$GEMINI_AUTH_LABEL" > "$GEMINI_PROFILE"
|
|
3111
|
+
printf "export GEMINI_HOME=%q\n" "${config.geminiHome}" >> "$GEMINI_PROFILE"
|
|
3112
|
+
printf "export GEMINI_CLI_DISABLE_UPDATE_CHECK=true\n" >> "$GEMINI_PROFILE"
|
|
3113
|
+
printf "export GEMINI_CLI_NONINTERACTIVE=true\n" >> "$GEMINI_PROFILE"
|
|
3114
|
+
printf "export GEMINI_CLI_APPROVAL_MODE=yolo\n" >> "$GEMINI_PROFILE"
|
|
3115
|
+
printf "alias gemini='/usr/local/bin/gemini-wrapper'\n" >> "$GEMINI_PROFILE"
|
|
3096
3116
|
cat <<'EOF' >> "$GEMINI_PROFILE"
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
if [[ -f "$GEMINI_API_KEY_FILE" ]]; then
|
|
3100
|
-
export GEMINI_API_KEY="$(tr -d '\r\n' < "$GEMINI_API_KEY_FILE")"
|
|
3101
|
-
elif [[ -f "$GEMINI_ENV_FILE" ]]; then
|
|
3102
|
-
GEMINI_KEY="$(grep -E '^GEMINI_API_KEY=' "$GEMINI_ENV_FILE" 2>/dev/null | head -1 | cut -d'=' -f2- | tr -d '\r\n' | sed "s/^['\"]//;s/['\"]$//")"
|
|
3103
|
-
if [[ -n "$GEMINI_KEY" ]]; then
|
|
3104
|
-
export GEMINI_API_KEY="$GEMINI_KEY"
|
|
3105
|
-
fi
|
|
3117
|
+
if [[ -f "$GEMINI_HOME/.api-key" ]]; then
|
|
3118
|
+
export GEMINI_API_KEY="$(cat "$GEMINI_HOME/.api-key" | tr -d '\r\n')"
|
|
3106
3119
|
fi
|
|
3107
3120
|
EOF
|
|
3108
3121
|
chmod 0644 "$GEMINI_PROFILE" || true
|
|
3109
3122
|
|
|
3110
|
-
docker_git_upsert_ssh_env "GEMINI_AUTH_LABEL" "$
|
|
3111
|
-
docker_git_upsert_ssh_env "GEMINI_API_KEY" "
|
|
3123
|
+
docker_git_upsert_ssh_env "GEMINI_AUTH_LABEL" "$GEMINI_AUTH_LABEL"
|
|
3124
|
+
docker_git_upsert_ssh_env "GEMINI_API_KEY" "\${GEMINI_API_KEY:-}"
|
|
3125
|
+
docker_git_upsert_ssh_env "GEMINI_CLI_DISABLE_UPDATE_CHECK" "true"
|
|
3126
|
+
docker_git_upsert_ssh_env "GEMINI_CLI_NONINTERACTIVE" "true"
|
|
3127
|
+
docker_git_upsert_ssh_env "GEMINI_CLI_APPROVAL_MODE" "yolo"`;
|
|
3128
|
+
var entrypointGeminiNoticeTemplate = String.raw`# Ensure global GEMINI.md exists for container context
|
|
3129
|
+
GEMINI_MD_PATH="__GEMINI_HOME__/GEMINI.md"
|
|
3130
|
+
GEMINI_WORKSPACE_CONTEXT="Контекст workspace: repository"
|
|
3131
|
+
if [[ "$REPO_REF" == issue-* ]]; then
|
|
3132
|
+
ISSUE_ID="$(printf "%s" "$REPO_REF" | sed -E 's#^issue-##')"
|
|
3133
|
+
ISSUE_URL=""
|
|
3134
|
+
if [[ "$REPO_URL" == https://github.com/* ]]; then
|
|
3135
|
+
ISSUE_REPO="$(printf "%s" "$REPO_URL" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')"
|
|
3136
|
+
if [[ -n "$ISSUE_REPO" ]]; then
|
|
3137
|
+
ISSUE_URL="https://github.com/$ISSUE_REPO/issues/$ISSUE_ID"
|
|
3138
|
+
fi
|
|
3139
|
+
fi
|
|
3140
|
+
if [[ -n "$ISSUE_URL" ]]; then
|
|
3141
|
+
GEMINI_WORKSPACE_CONTEXT="Контекст workspace: issue #$ISSUE_ID ($ISSUE_URL)"
|
|
3142
|
+
else
|
|
3143
|
+
GEMINI_WORKSPACE_CONTEXT="Контекст workspace: issue #$ISSUE_ID"
|
|
3144
|
+
fi
|
|
3145
|
+
elif [[ "$REPO_REF" == refs/pull/*/head ]]; then
|
|
3146
|
+
PR_ID="$(printf "%s" "$REPO_REF" | sed -nE 's#^refs/pull/([0-9]+)/head$#\1#p')"
|
|
3147
|
+
PR_URL=""
|
|
3148
|
+
if [[ "$REPO_URL" == https://github.com/* && -n "$PR_ID" ]]; then
|
|
3149
|
+
PR_REPO="$(printf "%s" "$REPO_URL" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')"
|
|
3150
|
+
if [[ -n "$PR_REPO" ]]; then
|
|
3151
|
+
PR_URL="https://github.com/$PR_REPO/pull/$PR_ID"
|
|
3152
|
+
fi
|
|
3153
|
+
fi
|
|
3154
|
+
if [[ -n "$PR_ID" && -n "$PR_URL" ]]; then
|
|
3155
|
+
GEMINI_WORKSPACE_CONTEXT="Контекст workspace: PR #$PR_ID ($PR_URL)"
|
|
3156
|
+
elif [[ -n "$PR_ID" ]]; then
|
|
3157
|
+
GEMINI_WORKSPACE_CONTEXT="Контекст workspace: PR #$PR_ID"
|
|
3158
|
+
else
|
|
3159
|
+
GEMINI_WORKSPACE_CONTEXT="Контекст workspace: pull request ($REPO_REF)"
|
|
3160
|
+
fi
|
|
3161
|
+
fi
|
|
3162
|
+
|
|
3163
|
+
cat <<EOF > "$GEMINI_MD_PATH"
|
|
3164
|
+
<!-- docker-git-managed:gemini-md -->
|
|
3165
|
+
Ты автономный агент, который имеет полностью все права управления контейнером. У тебя есть доступ к командам sudo, gh, codex, gemini, claude, opencode, oh-my-opencode, sshpass, git, node, pnpm и всем остальным другим. Проекты с которыми идёт работа лежат по пути ~
|
|
3166
|
+
Рабочая папка проекта (git clone): __TARGET_DIR__
|
|
3167
|
+
Доступные workspace пути: __TARGET_DIR__
|
|
3168
|
+
$GEMINI_WORKSPACE_CONTEXT
|
|
3169
|
+
Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__
|
|
3170
|
+
Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе.
|
|
3171
|
+
Для решения задач обязательно используй subagents. Сам агент обязан выполнять финальную проверку, интеграцию и валидацию результата перед ответом пользователю.
|
|
3172
|
+
Если ты видишь файлы AGENTS.md, GEMINI.md или CLAUDE.md внутри проекта, ты обязан их читать и соблюдать инструкции.
|
|
3173
|
+
<!-- /docker-git-managed:gemini-md -->
|
|
3174
|
+
EOF
|
|
3175
|
+
chown 1000:1000 "$GEMINI_MD_PATH" || true`;
|
|
3176
|
+
var renderEntrypointGeminiNotice = (config) => entrypointGeminiNoticeTemplate.replaceAll("__GEMINI_HOME__", config.geminiHome).replaceAll("__TARGET_DIR__", config.targetDir);
|
|
3112
3177
|
var renderEntrypointGeminiConfig = (config) => [
|
|
3113
3178
|
renderGeminiAuthConfig(config),
|
|
3114
|
-
|
|
3115
|
-
|
|
3179
|
+
renderGeminiPermissionSettingsConfig(config),
|
|
3180
|
+
renderGeminiMcpPlaywrightConfig(config),
|
|
3181
|
+
renderGeminiSudoConfig(config),
|
|
3182
|
+
renderGeminiProfileSetup(config),
|
|
3183
|
+
renderEntrypointGeminiNotice(config)
|
|
3116
3184
|
].join("\n\n");
|
|
3117
3185
|
//#endregion
|
|
3118
3186
|
//#region ../lib/src/core/templates-entrypoint/git.ts
|
|
@@ -3680,6 +3748,7 @@ AGENT_ENV_FILE="/run/docker-git/agent-env.sh"
|
|
|
3680
3748
|
{
|
|
3681
3749
|
[[ -f /etc/profile.d/gh-token.sh ]] && cat /etc/profile.d/gh-token.sh
|
|
3682
3750
|
[[ -f /etc/profile.d/claude-config.sh ]] && cat /etc/profile.d/claude-config.sh
|
|
3751
|
+
[[ -f /etc/profile.d/gemini-config.sh ]] && cat /etc/profile.d/gemini-config.sh
|
|
3683
3752
|
} > "$AGENT_ENV_FILE" 2>/dev/null || true
|
|
3684
3753
|
chmod 644 "$AGENT_ENV_FILE"`,
|
|
3685
3754
|
renderAgentPrompt(),
|
|
@@ -3689,7 +3758,7 @@ if [[ -n "$AGENT_PROMPT" ]]; then
|
|
|
3689
3758
|
chmod 644 "$AGENT_PROMPT_FILE"
|
|
3690
3759
|
fi`
|
|
3691
3760
|
].join("\n\n");
|
|
3692
|
-
var renderAgentPromptCommand = (mode) => mode
|
|
3761
|
+
var renderAgentPromptCommand = (mode) => Match.value(mode).pipe(Match.when("claude", () => String.raw`claude --dangerously-skip-permissions -p \"\$(cat \"$AGENT_PROMPT_FILE\")\"`), Match.when("codex", () => String.raw`codex exec \"\$(cat \"$AGENT_PROMPT_FILE\")\"`), Match.when("gemini", () => String.raw`gemini --approval-mode=yolo \"\$(cat \"$AGENT_PROMPT_FILE\")\"`), Match.exhaustive);
|
|
3693
3762
|
var renderAgentAutoLaunchCommand = (config, mode) => String.raw`su - ${config.sshUser} -s /bin/bash -c "bash -lc '. /etc/profile 2>/dev/null || true; . \"$AGENT_ENV_FILE\" 2>/dev/null || true; cd \"$TARGET_DIR\" && ${renderAgentPromptCommand(mode)}'"`;
|
|
3694
3763
|
var renderAgentModeBlock = (config, mode) => {
|
|
3695
3764
|
const startMessage = `[agent] starting ${mode}...`;
|
|
@@ -3710,6 +3779,7 @@ var renderAgentModeCase = (config) => [
|
|
|
3710
3779
|
String.raw`case "$AGENT_MODE" in`,
|
|
3711
3780
|
indentBlock(renderAgentModeBlock(config, "claude")),
|
|
3712
3781
|
indentBlock(renderAgentModeBlock(config, "codex")),
|
|
3782
|
+
indentBlock(renderAgentModeBlock(config, "gemini")),
|
|
3713
3783
|
indentBlock(String.raw`*)
|
|
3714
3784
|
echo "[agent] unknown agent mode: $AGENT_MODE"
|
|
3715
3785
|
;;`),
|
|
@@ -4182,7 +4252,9 @@ RUN set -eu; \
|
|
|
4182
4252
|
npm install -g oh-my-opencode@latest "oh-my-opencode-linux-\${OH_MY_OPENCODE_ARCH}@latest"
|
|
4183
4253
|
RUN oh-my-opencode --version
|
|
4184
4254
|
RUN npm install -g @anthropic-ai/claude-code@latest
|
|
4185
|
-
RUN claude --version
|
|
4255
|
+
RUN claude --version
|
|
4256
|
+
RUN npm install -g @google/gemini-cli@latest --force
|
|
4257
|
+
RUN gemini --version`;
|
|
4186
4258
|
var renderDockerfileOpenCode = () => `# Tooling: OpenCode (binary)
|
|
4187
4259
|
RUN set -eu; \
|
|
4188
4260
|
for attempt in 1 2 3 4 5; do \
|
|
@@ -6494,6 +6566,150 @@ var authCodexStatus = (command) => withCodexAuth(command, ({ accountPath, cwd })
|
|
|
6494
6566
|
}));
|
|
6495
6567
|
var authCodexLogout = (command) => withCodexAuth(command, ({ accountPath, cwd }) => runCodexLogout(cwd, accountPath)).pipe(Effect.zipRight(autoSyncState(`chore(state): auth codex logout ${normalizeAccountLabel(command.label, "default")}`)));
|
|
6496
6568
|
//#endregion
|
|
6569
|
+
//#region ../lib/src/usecases/auth-gemini-helpers.ts
|
|
6570
|
+
var geminiImageName = "docker-git-auth-gemini:latest";
|
|
6571
|
+
var geminiImageDir = ".docker-git/.orch/auth/gemini/.image";
|
|
6572
|
+
var geminiContainerHomeDir = "/gemini-home";
|
|
6573
|
+
var geminiCredentialsDir$1 = ".gemini";
|
|
6574
|
+
var geminiAuthRoot = ".docker-git/.orch/auth/gemini";
|
|
6575
|
+
var geminiApiKeyFileName = ".api-key";
|
|
6576
|
+
var geminiEnvFileName = ".env";
|
|
6577
|
+
var geminiApiKeyPath = (accountPath) => `${accountPath}/${geminiApiKeyFileName}`;
|
|
6578
|
+
var geminiEnvFilePath = (accountPath) => `${accountPath}/${geminiEnvFileName}`;
|
|
6579
|
+
var geminiCredentialsPath = (accountPath) => `${accountPath}/${geminiCredentialsDir$1}`;
|
|
6580
|
+
var renderGeminiDockerfile = () => String.raw`FROM ubuntu:24.04
|
|
6581
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
6582
|
+
RUN apt-get update \
|
|
6583
|
+
&& apt-get install -y --no-install-recommends ca-certificates curl bsdutils \
|
|
6584
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
6585
|
+
RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
|
|
6586
|
+
&& apt-get install -y --no-install-recommends nodejs \
|
|
6587
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
6588
|
+
RUN npm install -g @google/gemini-cli@0.33.2
|
|
6589
|
+
`;
|
|
6590
|
+
var ensureGeminiOrchLayout = (cwd) => migrateLegacyOrchLayout(cwd, {
|
|
6591
|
+
envGlobalPath: defaultTemplateConfig.envGlobalPath,
|
|
6592
|
+
envProjectPath: defaultTemplateConfig.envProjectPath,
|
|
6593
|
+
codexAuthPath: defaultTemplateConfig.codexAuthPath,
|
|
6594
|
+
ghAuthPath: ".docker-git/.orch/auth/gh",
|
|
6595
|
+
claudeAuthPath: ".docker-git/.orch/auth/claude",
|
|
6596
|
+
geminiAuthPath: ".docker-git/.orch/auth/gemini"
|
|
6597
|
+
});
|
|
6598
|
+
var resolveGeminiAccountPath = (path, rootPath, label) => {
|
|
6599
|
+
const accountLabel = normalizeAccountLabel(label, "default");
|
|
6600
|
+
return {
|
|
6601
|
+
accountLabel,
|
|
6602
|
+
accountPath: path.join(rootPath, accountLabel)
|
|
6603
|
+
};
|
|
6604
|
+
};
|
|
6605
|
+
var withGeminiAuth = (command, run, options = {}) => withFsPathContext(({ cwd, fs, path }) => Effect.gen(function* (_) {
|
|
6606
|
+
yield* _(ensureGeminiOrchLayout(cwd));
|
|
6607
|
+
const { accountLabel, accountPath } = resolveGeminiAccountPath(path, resolvePathFromCwd(path, cwd, command.geminiAuthPath), command.label);
|
|
6608
|
+
yield* _(fs.makeDirectory(accountPath, { recursive: true }));
|
|
6609
|
+
if (options.buildImage === true) yield* _(ensureDockerImage(fs, path, cwd, {
|
|
6610
|
+
imageName: geminiImageName,
|
|
6611
|
+
imageDir: geminiImageDir,
|
|
6612
|
+
dockerfile: renderGeminiDockerfile(),
|
|
6613
|
+
buildLabel: "gemini auth"
|
|
6614
|
+
}));
|
|
6615
|
+
return yield* _(run({
|
|
6616
|
+
accountLabel,
|
|
6617
|
+
accountPath,
|
|
6618
|
+
cwd,
|
|
6619
|
+
fs
|
|
6620
|
+
}));
|
|
6621
|
+
}));
|
|
6622
|
+
var readApiKey = (fs, accountPath) => Effect.gen(function* (_) {
|
|
6623
|
+
const apiKeyFilePath = geminiApiKeyPath(accountPath);
|
|
6624
|
+
if (yield* _(isRegularFile$1(fs, apiKeyFilePath))) {
|
|
6625
|
+
const trimmed = (yield* _(fs.readFileString(apiKeyFilePath), Effect.orElseSucceed(() => ""))).trim();
|
|
6626
|
+
if (trimmed.length > 0) return trimmed;
|
|
6627
|
+
}
|
|
6628
|
+
const envFilePath = geminiEnvFilePath(accountPath);
|
|
6629
|
+
if (yield* _(isRegularFile$1(fs, envFilePath))) {
|
|
6630
|
+
const lines = (yield* _(fs.readFileString(envFilePath), Effect.orElseSucceed(() => ""))).split("\n");
|
|
6631
|
+
for (const line of lines) {
|
|
6632
|
+
const trimmed = line.trim();
|
|
6633
|
+
if (trimmed.startsWith("GEMINI_API_KEY=")) {
|
|
6634
|
+
const value = trimmed.slice(15).replaceAll(/^['"]|['"]$/g, "").trim();
|
|
6635
|
+
if (value.length > 0) return value;
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6638
|
+
}
|
|
6639
|
+
return null;
|
|
6640
|
+
});
|
|
6641
|
+
var hasOauthCredentials$1 = (fs, accountPath) => Effect.gen(function* (_) {
|
|
6642
|
+
const credentialsDir = geminiCredentialsPath(accountPath);
|
|
6643
|
+
if (!(yield* _(fs.exists(credentialsDir)))) return false;
|
|
6644
|
+
const possibleFiles = [
|
|
6645
|
+
`${credentialsDir}/oauth-tokens.json`,
|
|
6646
|
+
`${credentialsDir}/credentials.json`,
|
|
6647
|
+
`${credentialsDir}/application_default_credentials.json`
|
|
6648
|
+
];
|
|
6649
|
+
for (const filePath of possibleFiles) if (yield* _(isRegularFile$1(fs, filePath))) return true;
|
|
6650
|
+
return false;
|
|
6651
|
+
});
|
|
6652
|
+
var resolveGeminiAuthMethod = (fs, accountPath) => Effect.gen(function* (_) {
|
|
6653
|
+
if ((yield* _(readApiKey(fs, accountPath))) !== null) return "api-key";
|
|
6654
|
+
return (yield* _(hasOauthCredentials$1(fs, accountPath))) ? "oauth" : "none";
|
|
6655
|
+
});
|
|
6656
|
+
var prepareGeminiCredentialsDir = (cwd, accountPath, fs) => Effect.gen(function* (_) {
|
|
6657
|
+
const credentialsDir = geminiCredentialsPath(accountPath);
|
|
6658
|
+
const removeFallback = pipe(runCommandExitCode({
|
|
6659
|
+
cwd,
|
|
6660
|
+
command: "docker",
|
|
6661
|
+
args: [
|
|
6662
|
+
"run",
|
|
6663
|
+
"--rm",
|
|
6664
|
+
"-v",
|
|
6665
|
+
`${accountPath}:/target`,
|
|
6666
|
+
"alpine",
|
|
6667
|
+
"rm",
|
|
6668
|
+
"-rf",
|
|
6669
|
+
"/target/.gemini"
|
|
6670
|
+
]
|
|
6671
|
+
}), Effect.asVoid, Effect.orElse(() => Effect.void));
|
|
6672
|
+
yield* _(fs.remove(credentialsDir, {
|
|
6673
|
+
recursive: true,
|
|
6674
|
+
force: true
|
|
6675
|
+
}).pipe(Effect.orElse(() => removeFallback)));
|
|
6676
|
+
yield* _(fs.makeDirectory(credentialsDir, { recursive: true }));
|
|
6677
|
+
yield* _(runCommandExitCode({
|
|
6678
|
+
cwd,
|
|
6679
|
+
command: "chmod",
|
|
6680
|
+
args: [
|
|
6681
|
+
"-R",
|
|
6682
|
+
"777",
|
|
6683
|
+
credentialsDir
|
|
6684
|
+
]
|
|
6685
|
+
}).pipe(Effect.orElse(() => Effect.succeed(0))));
|
|
6686
|
+
return credentialsDir;
|
|
6687
|
+
});
|
|
6688
|
+
var writeInitialSettings = (credentialsDir, fs) => Effect.gen(function* (_) {
|
|
6689
|
+
const settingsPath = `${credentialsDir}/settings.json`;
|
|
6690
|
+
yield* _(fs.writeFileString(settingsPath, JSON.stringify({
|
|
6691
|
+
model: {
|
|
6692
|
+
name: "gemini-2.0-flash",
|
|
6693
|
+
compressionThreshold: .9,
|
|
6694
|
+
disableLoopDetection: true
|
|
6695
|
+
},
|
|
6696
|
+
general: { defaultApprovalMode: "auto_edit" },
|
|
6697
|
+
yolo: true,
|
|
6698
|
+
sandbox: { enabled: false },
|
|
6699
|
+
security: {
|
|
6700
|
+
folderTrust: { enabled: false },
|
|
6701
|
+
auth: { selectedType: "oauth-personal" },
|
|
6702
|
+
approvalPolicy: "never"
|
|
6703
|
+
}
|
|
6704
|
+
}, null, 2)));
|
|
6705
|
+
const trustedFoldersPath = `${credentialsDir}/trustedFolders.json`;
|
|
6706
|
+
yield* _(fs.writeFileString(trustedFoldersPath, JSON.stringify({
|
|
6707
|
+
"/": "TRUST_FOLDER",
|
|
6708
|
+
[geminiContainerHomeDir]: "TRUST_FOLDER"
|
|
6709
|
+
})));
|
|
6710
|
+
return settingsPath;
|
|
6711
|
+
});
|
|
6712
|
+
//#endregion
|
|
6497
6713
|
//#region ../lib/src/usecases/auth-gemini-oauth.ts
|
|
6498
6714
|
var outputWindowSize = 262144;
|
|
6499
6715
|
var authSuccessPatterns = [
|
|
@@ -6512,22 +6728,29 @@ var authFailurePatterns = [
|
|
|
6512
6728
|
];
|
|
6513
6729
|
var detectAuthResult = (output) => {
|
|
6514
6730
|
const normalized = stripAnsi(output).toLowerCase();
|
|
6515
|
-
|
|
6516
|
-
|
|
6731
|
+
const authInitiated = [
|
|
6732
|
+
"please visit the following url",
|
|
6733
|
+
"enter the authorization code",
|
|
6734
|
+
"authorized the application"
|
|
6735
|
+
].some((m) => normalized.includes(m));
|
|
6736
|
+
if (authSuccessPatterns.some((pattern) => normalized.includes(pattern.toLowerCase()) && (authInitiated || normalized.includes("logged in with google")))) return "success";
|
|
6737
|
+
if (authFailurePatterns.some((pattern) => normalized.includes(pattern.toLowerCase()))) return "failure";
|
|
6517
6738
|
return "pending";
|
|
6518
6739
|
};
|
|
6519
|
-
var
|
|
6520
|
-
var buildDockerGeminiAuthSpec = (cwd, accountPath, image, containerPath) => ({
|
|
6740
|
+
var defaultGeminiOauthCallbackPort = 38751;
|
|
6741
|
+
var buildDockerGeminiAuthSpec = (cwd, accountPath, image, containerPath, port) => ({
|
|
6521
6742
|
cwd,
|
|
6522
6743
|
image,
|
|
6523
6744
|
hostPath: accountPath,
|
|
6524
6745
|
containerPath,
|
|
6525
|
-
callbackPort:
|
|
6746
|
+
callbackPort: port,
|
|
6526
6747
|
env: [
|
|
6527
6748
|
`HOME=${containerPath}`,
|
|
6528
6749
|
"NO_BROWSER=true",
|
|
6529
|
-
"GEMINI_CLI_NONINTERACTIVE=
|
|
6530
|
-
|
|
6750
|
+
"GEMINI_CLI_NONINTERACTIVE=true",
|
|
6751
|
+
"GEMINI_CLI_TRUST_ALL=true",
|
|
6752
|
+
"GEMINI_DISABLE_UPDATE_CHECK=true",
|
|
6753
|
+
`OAUTH_CALLBACK_PORT=${port}`,
|
|
6531
6754
|
"OAUTH_CALLBACK_HOST=0.0.0.0"
|
|
6532
6755
|
]
|
|
6533
6756
|
});
|
|
@@ -6535,15 +6758,16 @@ var buildDockerGeminiAuthArgs = (spec) => {
|
|
|
6535
6758
|
const base = [
|
|
6536
6759
|
"run",
|
|
6537
6760
|
"--rm",
|
|
6761
|
+
"--init",
|
|
6538
6762
|
"-i",
|
|
6539
6763
|
"-t",
|
|
6540
6764
|
"-v",
|
|
6541
6765
|
`${spec.hostPath}:${spec.containerPath}`,
|
|
6542
6766
|
"-p",
|
|
6543
|
-
`${spec.callbackPort}:${spec.callbackPort}
|
|
6767
|
+
`${spec.callbackPort}:${spec.callbackPort}`,
|
|
6768
|
+
"-w",
|
|
6769
|
+
spec.containerPath
|
|
6544
6770
|
];
|
|
6545
|
-
const dockerUser = resolveDefaultDockerUser();
|
|
6546
|
-
if (dockerUser !== null) base.push("--user", dockerUser);
|
|
6547
6771
|
for (const entry of spec.env) {
|
|
6548
6772
|
const trimmed = entry.trim();
|
|
6549
6773
|
if (trimmed.length === 0) continue;
|
|
@@ -6553,21 +6777,52 @@ var buildDockerGeminiAuthArgs = (spec) => {
|
|
|
6553
6777
|
...base,
|
|
6554
6778
|
spec.image,
|
|
6555
6779
|
"gemini",
|
|
6780
|
+
"mcp",
|
|
6781
|
+
"list",
|
|
6556
6782
|
"--debug"
|
|
6557
6783
|
];
|
|
6558
6784
|
};
|
|
6785
|
+
var cleanupExistingContainers = (port) => Effect.gen(function* (_) {
|
|
6786
|
+
const ids = (yield* _(runCommandCapture({
|
|
6787
|
+
cwd: process.cwd(),
|
|
6788
|
+
command: "docker",
|
|
6789
|
+
args: [
|
|
6790
|
+
"ps",
|
|
6791
|
+
"-q",
|
|
6792
|
+
"--filter",
|
|
6793
|
+
`publish=${port}`
|
|
6794
|
+
]
|
|
6795
|
+
}, [0], () => /* @__PURE__ */ new Error("docker ps failed")).pipe(Effect.map((value) => value.trim()), Effect.orElseSucceed(() => "")))).split("\n").filter(Boolean);
|
|
6796
|
+
if (ids.length > 0) {
|
|
6797
|
+
yield* _(Effect.logInfo(`Cleaning up existing containers using port ${port}: ${ids.join(", ")}`));
|
|
6798
|
+
yield* _(runCommandExitCode({
|
|
6799
|
+
cwd: process.cwd(),
|
|
6800
|
+
command: "docker",
|
|
6801
|
+
args: [
|
|
6802
|
+
"rm",
|
|
6803
|
+
"-f",
|
|
6804
|
+
...ids
|
|
6805
|
+
]
|
|
6806
|
+
}).pipe(Effect.orElse(() => Effect.succeed(0))));
|
|
6807
|
+
}
|
|
6808
|
+
});
|
|
6559
6809
|
var startDockerProcess = (executor, spec) => executor.start(pipe(Command.make("docker", ...buildDockerGeminiAuthArgs(spec)), Command.workingDirectory(spec.cwd), Command.stdin("inherit"), Command.stdout("pipe"), Command.stderr("pipe")));
|
|
6560
|
-
var pumpDockerOutput = (source, fd, resultBox) => {
|
|
6810
|
+
var pumpDockerOutput = (source, fd, resultBox, authDeferred) => {
|
|
6561
6811
|
const decoder = new TextDecoder("utf-8");
|
|
6562
6812
|
let outputWindow = "";
|
|
6563
|
-
return pipe(source, Stream.runForEach((chunk) => Effect.
|
|
6564
|
-
|
|
6813
|
+
return pipe(source, Stream.runForEach((chunk) => Effect.gen(function* (_) {
|
|
6814
|
+
yield* _(Effect.sync(() => {
|
|
6815
|
+
writeChunkToFd(fd, chunk);
|
|
6816
|
+
}));
|
|
6565
6817
|
outputWindow += decoder.decode(chunk);
|
|
6566
6818
|
if (outputWindow.length > outputWindowSize) outputWindow = outputWindow.slice(-outputWindowSize);
|
|
6567
6819
|
if (resultBox.value !== "pending") return;
|
|
6568
6820
|
const result = detectAuthResult(outputWindow);
|
|
6569
|
-
if (result !== "pending")
|
|
6570
|
-
|
|
6821
|
+
if (result !== "pending") {
|
|
6822
|
+
resultBox.value = result;
|
|
6823
|
+
if (result === "success") yield* _(Deferred.succeed(authDeferred, void 0));
|
|
6824
|
+
}
|
|
6825
|
+
}))).pipe(Effect.asVoid);
|
|
6571
6826
|
};
|
|
6572
6827
|
var resolveGeminiLoginResult = (result, exitCode) => Effect.gen(function* (_) {
|
|
6573
6828
|
if (result === "success") {
|
|
@@ -6581,7 +6836,7 @@ var resolveGeminiLoginResult = (result, exitCode) => Effect.gen(function* (_) {
|
|
|
6581
6836
|
})));
|
|
6582
6837
|
});
|
|
6583
6838
|
var printOauthInstructions = () => Effect.sync(() => {
|
|
6584
|
-
const port =
|
|
6839
|
+
const port = defaultGeminiOauthCallbackPort;
|
|
6585
6840
|
process.stderr.write("\n");
|
|
6586
6841
|
process.stderr.write("╔═══════════════════════════════════════════════════════════════════════════╗\n");
|
|
6587
6842
|
process.stderr.write("║ Gemini CLI OAuth Authentication ║\n");
|
|
@@ -6593,108 +6848,41 @@ var printOauthInstructions = () => Effect.sync(() => {
|
|
|
6593
6848
|
process.stderr.write("╚═══════════════════════════════════════════════════════════════════════════╝\n");
|
|
6594
6849
|
process.stderr.write("\n");
|
|
6595
6850
|
});
|
|
6851
|
+
var fixGeminiAuthPermissions = (hostPath, containerPath) => runCommandExitCode({
|
|
6852
|
+
cwd: process.cwd(),
|
|
6853
|
+
command: "docker",
|
|
6854
|
+
args: [
|
|
6855
|
+
"run",
|
|
6856
|
+
"--rm",
|
|
6857
|
+
"-v",
|
|
6858
|
+
`${hostPath}:${containerPath}`,
|
|
6859
|
+
"alpine",
|
|
6860
|
+
"chmod",
|
|
6861
|
+
"-R",
|
|
6862
|
+
"777",
|
|
6863
|
+
containerPath
|
|
6864
|
+
]
|
|
6865
|
+
}).pipe(Effect.tapError((err) => Effect.logWarning(`Failed to fix Gemini auth permissions: ${String(err)}`)), Effect.orElse(() => Effect.succeed(0)));
|
|
6596
6866
|
var runGeminiOauthLoginWithPrompt = (cwd, accountPath, options) => Effect.scoped(Effect.gen(function* (_) {
|
|
6867
|
+
const port = defaultGeminiOauthCallbackPort;
|
|
6868
|
+
yield* _(cleanupExistingContainers(port));
|
|
6597
6869
|
yield* _(printOauthInstructions());
|
|
6598
|
-
const
|
|
6870
|
+
const executor = yield* _(CommandExecutor.CommandExecutor);
|
|
6871
|
+
const hostPath = yield* _(resolveDockerVolumeHostPath(cwd, accountPath));
|
|
6872
|
+
const spec = buildDockerGeminiAuthSpec(cwd, hostPath, options.image, options.containerPath, port);
|
|
6873
|
+
const proc = yield* _(startDockerProcess(executor, spec));
|
|
6874
|
+
const authDeferred = yield* _(Deferred.make());
|
|
6599
6875
|
const resultBox = { value: "pending" };
|
|
6600
|
-
const stdoutFiber = yield* _(Effect.forkScoped(pumpDockerOutput(proc.stdout, 1, resultBox)));
|
|
6601
|
-
const stderrFiber = yield* _(Effect.forkScoped(pumpDockerOutput(proc.stderr, 2, resultBox)));
|
|
6602
|
-
const exitCode = yield* _(proc.exitCode.pipe(Effect.map(Number)));
|
|
6603
|
-
yield* _(Fiber$1.
|
|
6604
|
-
yield* _(Fiber$1.
|
|
6876
|
+
const stdoutFiber = yield* _(Effect.forkScoped(pumpDockerOutput(proc.stdout, 1, resultBox, authDeferred)));
|
|
6877
|
+
const stderrFiber = yield* _(Effect.forkScoped(pumpDockerOutput(proc.stderr, 2, resultBox, authDeferred)));
|
|
6878
|
+
const exitCode = yield* _(Effect.race(proc.exitCode.pipe(Effect.map(Number)), pipe(Deferred.await(authDeferred), Effect.delay("500 millis"), Effect.flatMap(() => proc.kill()), Effect.map(() => 0))));
|
|
6879
|
+
yield* _(Fiber$1.interrupt(stdoutFiber));
|
|
6880
|
+
yield* _(Fiber$1.interrupt(stderrFiber));
|
|
6881
|
+
yield* _(fixGeminiAuthPermissions(hostPath, spec.containerPath));
|
|
6605
6882
|
return yield* _(resolveGeminiLoginResult(resultBox.value, exitCode));
|
|
6606
6883
|
}));
|
|
6607
6884
|
//#endregion
|
|
6608
6885
|
//#region ../lib/src/usecases/auth-gemini.ts
|
|
6609
|
-
var geminiImageName = "docker-git-auth-gemini:latest";
|
|
6610
|
-
var geminiImageDir = ".docker-git/.orch/auth/gemini/.image";
|
|
6611
|
-
var geminiContainerHomeDir = "/gemini-home";
|
|
6612
|
-
var geminiCredentialsDir$1 = ".gemini";
|
|
6613
|
-
var geminiAuthRoot = ".docker-git/.orch/auth/gemini";
|
|
6614
|
-
var geminiApiKeyFileName = ".api-key";
|
|
6615
|
-
var geminiEnvFileName = ".env";
|
|
6616
|
-
var geminiApiKeyPath = (accountPath) => `${accountPath}/${geminiApiKeyFileName}`;
|
|
6617
|
-
var geminiEnvFilePath = (accountPath) => `${accountPath}/${geminiEnvFileName}`;
|
|
6618
|
-
var geminiCredentialsPath = (accountPath) => `${accountPath}/${geminiCredentialsDir$1}`;
|
|
6619
|
-
var renderGeminiDockerfile = () => String.raw`FROM ubuntu:24.04
|
|
6620
|
-
ENV DEBIAN_FRONTEND=noninteractive
|
|
6621
|
-
RUN apt-get update \
|
|
6622
|
-
&& apt-get install -y --no-install-recommends ca-certificates curl bsdutils \
|
|
6623
|
-
&& rm -rf /var/lib/apt/lists/*
|
|
6624
|
-
RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
|
|
6625
|
-
&& apt-get install -y --no-install-recommends nodejs \
|
|
6626
|
-
&& node -v \
|
|
6627
|
-
&& npm -v \
|
|
6628
|
-
&& rm -rf /var/lib/apt/lists/*
|
|
6629
|
-
RUN npm install -g @google/gemini-cli@latest
|
|
6630
|
-
ENTRYPOINT ["/bin/bash", "-c"]
|
|
6631
|
-
`;
|
|
6632
|
-
var ensureGeminiOrchLayout = (cwd) => migrateLegacyOrchLayout(cwd, {
|
|
6633
|
-
envGlobalPath: defaultTemplateConfig.envGlobalPath,
|
|
6634
|
-
envProjectPath: defaultTemplateConfig.envProjectPath,
|
|
6635
|
-
codexAuthPath: defaultTemplateConfig.codexAuthPath,
|
|
6636
|
-
ghAuthPath: ".docker-git/.orch/auth/gh",
|
|
6637
|
-
claudeAuthPath: ".docker-git/.orch/auth/claude",
|
|
6638
|
-
geminiAuthPath: ".docker-git/.orch/auth/gemini"
|
|
6639
|
-
});
|
|
6640
|
-
var resolveGeminiAccountPath = (path, rootPath, label) => {
|
|
6641
|
-
const accountLabel = normalizeAccountLabel(label, "default");
|
|
6642
|
-
return {
|
|
6643
|
-
accountLabel,
|
|
6644
|
-
accountPath: path.join(rootPath, accountLabel)
|
|
6645
|
-
};
|
|
6646
|
-
};
|
|
6647
|
-
var withGeminiAuth = (command, run, options = {}) => withFsPathContext(({ cwd, fs, path }) => Effect.gen(function* (_) {
|
|
6648
|
-
yield* _(ensureGeminiOrchLayout(cwd));
|
|
6649
|
-
const { accountLabel, accountPath } = resolveGeminiAccountPath(path, resolvePathFromCwd(path, cwd, command.geminiAuthPath), command.label);
|
|
6650
|
-
yield* _(fs.makeDirectory(accountPath, { recursive: true }));
|
|
6651
|
-
if (options.buildImage === true) yield* _(ensureDockerImage(fs, path, cwd, {
|
|
6652
|
-
imageName: geminiImageName,
|
|
6653
|
-
imageDir: geminiImageDir,
|
|
6654
|
-
dockerfile: renderGeminiDockerfile(),
|
|
6655
|
-
buildLabel: "gemini auth"
|
|
6656
|
-
}));
|
|
6657
|
-
return yield* _(run({
|
|
6658
|
-
accountLabel,
|
|
6659
|
-
accountPath,
|
|
6660
|
-
cwd,
|
|
6661
|
-
fs
|
|
6662
|
-
}));
|
|
6663
|
-
}));
|
|
6664
|
-
var readApiKey = (fs, accountPath) => Effect.gen(function* (_) {
|
|
6665
|
-
const apiKeyFilePath = geminiApiKeyPath(accountPath);
|
|
6666
|
-
if (yield* _(isRegularFile$1(fs, apiKeyFilePath))) {
|
|
6667
|
-
const trimmed = (yield* _(fs.readFileString(apiKeyFilePath), Effect.orElseSucceed(() => ""))).trim();
|
|
6668
|
-
if (trimmed.length > 0) return trimmed;
|
|
6669
|
-
}
|
|
6670
|
-
const envFilePath = geminiEnvFilePath(accountPath);
|
|
6671
|
-
if (yield* _(isRegularFile$1(fs, envFilePath))) {
|
|
6672
|
-
const lines = (yield* _(fs.readFileString(envFilePath), Effect.orElseSucceed(() => ""))).split("\n");
|
|
6673
|
-
for (const line of lines) {
|
|
6674
|
-
const trimmed = line.trim();
|
|
6675
|
-
if (trimmed.startsWith("GEMINI_API_KEY=")) {
|
|
6676
|
-
const value = trimmed.slice(15).replaceAll(/^['"]|['"]$/g, "").trim();
|
|
6677
|
-
if (value.length > 0) return value;
|
|
6678
|
-
}
|
|
6679
|
-
}
|
|
6680
|
-
}
|
|
6681
|
-
return null;
|
|
6682
|
-
});
|
|
6683
|
-
var hasOauthCredentials$1 = (fs, accountPath) => Effect.gen(function* (_) {
|
|
6684
|
-
const credentialsDir = geminiCredentialsPath(accountPath);
|
|
6685
|
-
if (!(yield* _(fs.exists(credentialsDir)))) return false;
|
|
6686
|
-
const possibleFiles = [
|
|
6687
|
-
`${credentialsDir}/oauth-tokens.json`,
|
|
6688
|
-
`${credentialsDir}/credentials.json`,
|
|
6689
|
-
`${credentialsDir}/application_default_credentials.json`
|
|
6690
|
-
];
|
|
6691
|
-
for (const filePath of possibleFiles) if (yield* _(isRegularFile$1(fs, filePath))) return true;
|
|
6692
|
-
return false;
|
|
6693
|
-
});
|
|
6694
|
-
var resolveGeminiAuthMethod = (fs, accountPath) => Effect.gen(function* (_) {
|
|
6695
|
-
if ((yield* _(readApiKey(fs, accountPath))) !== null) return "api-key";
|
|
6696
|
-
return (yield* _(hasOauthCredentials$1(fs, accountPath))) ? "oauth" : "none";
|
|
6697
|
-
});
|
|
6698
6886
|
var authGeminiLogin = (command, apiKey) => {
|
|
6699
6887
|
const accountLabel = normalizeAccountLabel(command.label, "default");
|
|
6700
6888
|
return withGeminiAuth(command, ({ accountPath, fs }) => Effect.gen(function* (_) {
|
|
@@ -6718,12 +6906,16 @@ var authGeminiLoginCli = (_command) => Effect.gen(function* (_) {
|
|
|
6718
6906
|
var authGeminiLoginOauth = (command) => {
|
|
6719
6907
|
const accountLabel = normalizeAccountLabel(command.label, "default");
|
|
6720
6908
|
return withGeminiAuth(command, ({ accountPath, cwd, fs }) => Effect.gen(function* (_) {
|
|
6721
|
-
const
|
|
6722
|
-
yield* _(fs.makeDirectory(credentialsDir, { recursive: true }));
|
|
6909
|
+
const settingsPath = yield* _(writeInitialSettings(yield* _(prepareGeminiCredentialsDir(cwd, accountPath, fs)), fs));
|
|
6723
6910
|
yield* _(runGeminiOauthLoginWithPrompt(cwd, accountPath, {
|
|
6724
6911
|
image: geminiImageName,
|
|
6725
6912
|
containerPath: geminiContainerHomeDir
|
|
6726
6913
|
}));
|
|
6914
|
+
yield* _(fs.writeFileString(settingsPath, JSON.stringify({ security: {
|
|
6915
|
+
folderTrust: { enabled: false },
|
|
6916
|
+
auth: { selectedType: "oauth-personal" },
|
|
6917
|
+
approvalPolicy: "never"
|
|
6918
|
+
} }, null, 2) + "\n"));
|
|
6727
6919
|
}), { buildImage: true }).pipe(Effect.zipRight(autoSyncState(`chore(state): auth gemini oauth ${accountLabel}`)));
|
|
6728
6920
|
};
|
|
6729
6921
|
var authGeminiStatus = (command) => withGeminiAuth(command, ({ accountLabel, accountPath, fs }) => Effect.gen(function* (_) {
|
|
@@ -8098,7 +8290,8 @@ var buildClaudeCommand = (action, options) => Match.value(action).pipe(Match.whe
|
|
|
8098
8290
|
var buildGeminiCommand = (action, options) => Match.value(action).pipe(Match.when("login", () => Either.right({
|
|
8099
8291
|
_tag: "AuthGeminiLogin",
|
|
8100
8292
|
label: options.label,
|
|
8101
|
-
geminiAuthPath: options.geminiAuthPath
|
|
8293
|
+
geminiAuthPath: options.geminiAuthPath,
|
|
8294
|
+
isWeb: options.authWeb
|
|
8102
8295
|
})), Match.when("status", () => Either.right({
|
|
8103
8296
|
_tag: "AuthGeminiStatus",
|
|
8104
8297
|
label: options.label,
|
|
@@ -9588,12 +9781,14 @@ var resolveClaudeLogoutEffect = (labelOption) => authClaudeLogout({
|
|
|
9588
9781
|
var resolveGeminiOauthEffect = (labelOption) => authGeminiLoginOauth({
|
|
9589
9782
|
_tag: "AuthGeminiLogin",
|
|
9590
9783
|
label: labelOption,
|
|
9591
|
-
geminiAuthPath: geminiAuthRoot
|
|
9784
|
+
geminiAuthPath: geminiAuthRoot,
|
|
9785
|
+
isWeb: false
|
|
9592
9786
|
});
|
|
9593
9787
|
var resolveGeminiApiKeyEffect = (labelOption, apiKey) => authGeminiLogin({
|
|
9594
9788
|
_tag: "AuthGeminiLogin",
|
|
9595
9789
|
label: labelOption,
|
|
9596
|
-
geminiAuthPath: geminiAuthRoot
|
|
9790
|
+
geminiAuthPath: geminiAuthRoot,
|
|
9791
|
+
isWeb: false
|
|
9597
9792
|
}, apiKey);
|
|
9598
9793
|
var resolveGeminiLogoutEffect = (labelOption) => authGeminiLogout({
|
|
9599
9794
|
_tag: "AuthGeminiLogout",
|
|
@@ -11378,7 +11573,7 @@ var setExitCode = (code) => Effect.sync(() => {
|
|
|
11378
11573
|
});
|
|
11379
11574
|
var logWarningAndExit = (error) => pipe(Effect.logWarning(renderError(error)), Effect.tap(() => setExitCode(1)), Effect.asVoid);
|
|
11380
11575
|
var logErrorAndExit = (error) => pipe(Effect.logError(renderError(error)), Effect.tap(() => setExitCode(1)), Effect.asVoid);
|
|
11381
|
-
var handleNonBaseCommand = (command) => Match.value(command).pipe(Match.when({ _tag: "StatePath" }, () => statePath), Match.when({ _tag: "StateInit" }, (cmd) => stateInit(cmd)), Match.when({ _tag: "StateStatus" }, () => stateStatus), Match.when({ _tag: "StatePull" }, () => statePull), Match.when({ _tag: "StateCommit" }, (cmd) => stateCommit(cmd.message)), Match.when({ _tag: "StatePush" }, () => statePush), Match.when({ _tag: "StateSync" }, (cmd) => stateSync(cmd.message)), Match.when({ _tag: "AuthGithubLogin" }, (cmd) => authGithubLogin(cmd)), Match.when({ _tag: "AuthGithubStatus" }, (cmd) => authGithubStatus(cmd)), Match.when({ _tag: "AuthGithubLogout" }, (cmd) => authGithubLogout(cmd)), Match.when({ _tag: "AuthCodexLogin" }, (cmd) => authCodexLogin(cmd)), Match.when({ _tag: "AuthCodexStatus" }, (cmd) => authCodexStatus(cmd)), Match.when({ _tag: "AuthCodexLogout" }, (cmd) => authCodexLogout(cmd)), Match.when({ _tag: "AuthClaudeLogin" }, (cmd) => authClaudeLogin(cmd)), Match.when({ _tag: "AuthClaudeStatus" }, (cmd) => authClaudeStatus(cmd)), Match.when({ _tag: "AuthClaudeLogout" }, (cmd) => authClaudeLogout(cmd)), Match.when({ _tag: "Attach" }, (cmd) => attachTmux(cmd)), Match.when({ _tag: "Panes" }, (cmd) => listTmuxPanes(cmd)), Match.when({ _tag: "SessionsList" }, (cmd) => listTerminalSessions(cmd))).pipe(Match.when({ _tag: "AuthGeminiLogin" }, (cmd) => authGeminiLoginCli(cmd)), Match.when({ _tag: "AuthGeminiStatus" }, (cmd) => authGeminiStatus(cmd)), Match.when({ _tag: "AuthGeminiLogout" }, (cmd) => authGeminiLogout(cmd)), Match.when({ _tag: "SessionsKill" }, (cmd) => killTerminalProcess(cmd)), Match.when({ _tag: "Apply" }, (cmd) => applyProjectConfig(cmd)), Match.when({ _tag: "SessionsLogs" }, (cmd) => tailTerminalLogs(cmd)), Match.when({ _tag: "ScrapExport" }, (cmd) => exportScrap(cmd)), Match.when({ _tag: "ScrapImport" }, (cmd) => importScrap(cmd)), Match.when({ _tag: "McpPlaywrightUp" }, (cmd) => mcpPlaywrightUp(cmd)), Match.exhaustive);
|
|
11576
|
+
var handleNonBaseCommand = (command) => Match.value(command).pipe(Match.when({ _tag: "StatePath" }, () => statePath), Match.when({ _tag: "StateInit" }, (cmd) => stateInit(cmd)), Match.when({ _tag: "StateStatus" }, () => stateStatus), Match.when({ _tag: "StatePull" }, () => statePull), Match.when({ _tag: "StateCommit" }, (cmd) => stateCommit(cmd.message)), Match.when({ _tag: "StatePush" }, () => statePush), Match.when({ _tag: "StateSync" }, (cmd) => stateSync(cmd.message)), Match.when({ _tag: "AuthGithubLogin" }, (cmd) => authGithubLogin(cmd)), Match.when({ _tag: "AuthGithubStatus" }, (cmd) => authGithubStatus(cmd)), Match.when({ _tag: "AuthGithubLogout" }, (cmd) => authGithubLogout(cmd)), Match.when({ _tag: "AuthCodexLogin" }, (cmd) => authCodexLogin(cmd)), Match.when({ _tag: "AuthCodexStatus" }, (cmd) => authCodexStatus(cmd)), Match.when({ _tag: "AuthCodexLogout" }, (cmd) => authCodexLogout(cmd)), Match.when({ _tag: "AuthClaudeLogin" }, (cmd) => authClaudeLogin(cmd)), Match.when({ _tag: "AuthClaudeStatus" }, (cmd) => authClaudeStatus(cmd)), Match.when({ _tag: "AuthClaudeLogout" }, (cmd) => authClaudeLogout(cmd)), Match.when({ _tag: "Attach" }, (cmd) => attachTmux(cmd)), Match.when({ _tag: "Panes" }, (cmd) => listTmuxPanes(cmd)), Match.when({ _tag: "SessionsList" }, (cmd) => listTerminalSessions(cmd))).pipe(Match.when({ _tag: "AuthGeminiLogin" }, (cmd) => cmd.isWeb ? authGeminiLoginOauth(cmd) : authGeminiLoginCli(cmd)), Match.when({ _tag: "AuthGeminiStatus" }, (cmd) => authGeminiStatus(cmd)), Match.when({ _tag: "AuthGeminiLogout" }, (cmd) => authGeminiLogout(cmd)), Match.when({ _tag: "SessionsKill" }, (cmd) => killTerminalProcess(cmd)), Match.when({ _tag: "Apply" }, (cmd) => applyProjectConfig(cmd)), Match.when({ _tag: "SessionsLogs" }, (cmd) => tailTerminalLogs(cmd)), Match.when({ _tag: "ScrapExport" }, (cmd) => exportScrap(cmd)), Match.when({ _tag: "ScrapImport" }, (cmd) => importScrap(cmd)), Match.when({ _tag: "McpPlaywrightUp" }, (cmd) => mcpPlaywrightUp(cmd)), Match.exhaustive);
|
|
11382
11577
|
var program = pipe(readCommand, Effect.flatMap((command) => Match.value(command).pipe(Match.when({ _tag: "Help" }, ({ message }) => Effect.log(message)), Match.when({ _tag: "Create" }, (create) => createProject(create)), Match.when({ _tag: "Status" }, () => listProjectStatus), Match.when({ _tag: "DownAll" }, () => downAllDockerGitProjects), Match.when({ _tag: "Menu" }, () => runMenu), Match.orElse((cmd) => handleNonBaseCommand(cmd)))), Effect.catchTag("FileExistsError", (error) => pipe(Effect.logWarning(renderError(error)), Effect.asVoid)), Effect.catchTag("DockerAccessError", logWarningAndExit), Effect.catchTag("DockerCommandError", logWarningAndExit), Effect.catchTag("AuthError", logWarningAndExit), Effect.catchTag("AgentFailedError", logWarningAndExit), Effect.catchTag("CommandFailedError", logWarningAndExit), Effect.catchTag("ScrapArchiveNotFoundError", logErrorAndExit), Effect.catchTag("ScrapTargetDirUnsupportedError", logErrorAndExit), Effect.catchTag("ScrapWipeRefusedError", logErrorAndExit), Effect.matchEffect({
|
|
11383
11578
|
onFailure: (error) => isParseError(error) ? logErrorAndExit(error) : pipe(Effect.logError(renderError(error)), Effect.flatMap(() => Effect.fail(error))),
|
|
11384
11579
|
onSuccess: () => Effect.void
|