@prover-coder-ai/docker-git 1.0.43 → 1.0.45
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 +890 -154
- 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";
|
|
@@ -269,6 +269,8 @@ var defaultTemplateConfig = {
|
|
|
269
269
|
codexAuthPath: "./.docker-git/.orch/auth/codex",
|
|
270
270
|
codexSharedAuthPath: "./.docker-git/.orch/auth/codex",
|
|
271
271
|
codexHome: "/home/dev/.codex",
|
|
272
|
+
geminiAuthPath: "./.docker-git/.orch/auth/gemini",
|
|
273
|
+
geminiHome: "/home/dev/.gemini",
|
|
272
274
|
cpuLimit: "30%",
|
|
273
275
|
ramLimit: "30%",
|
|
274
276
|
dockerNetworkMode: defaultDockerNetworkMode,
|
|
@@ -809,6 +811,10 @@ var buildDockerAuthSpec = (input) => ({
|
|
|
809
811
|
args: input.args,
|
|
810
812
|
interactive: input.interactive
|
|
811
813
|
});
|
|
814
|
+
var isRegularFile$1 = (fs, filePath) => Effect.gen(function* (_) {
|
|
815
|
+
if (!(yield* _(fs.exists(filePath)))) return false;
|
|
816
|
+
return (yield* _(fs.stat(filePath))).type === "File";
|
|
817
|
+
});
|
|
812
818
|
//#endregion
|
|
813
819
|
//#region ../lib/src/usecases/auth-sync-helpers.ts
|
|
814
820
|
var JsonValueSchema = Schema.suspend(() => Schema.Union(Schema.Null, Schema.Boolean, Schema.String, Schema.JsonNumber, Schema.Array(JsonValueSchema), Schema.Record({
|
|
@@ -876,7 +882,7 @@ var autoOptionError = (reason) => ({
|
|
|
876
882
|
option: "--auto",
|
|
877
883
|
reason
|
|
878
884
|
});
|
|
879
|
-
var isRegularFile
|
|
885
|
+
var isRegularFile = (fs, filePath) => Effect.gen(function* (_) {
|
|
880
886
|
if (!(yield* _(fs.exists(filePath)))) return false;
|
|
881
887
|
return (yield* _(fs.stat(filePath))).type === "File";
|
|
882
888
|
});
|
|
@@ -892,8 +898,8 @@ var resolveClaudeAccountPath$1 = (rootPath, label) => {
|
|
|
892
898
|
var hasClaudeAuth = (fs, rootPath, label) => Effect.gen(function* (_) {
|
|
893
899
|
for (const accountPath of resolveClaudeAccountPath$1(rootPath, label)) {
|
|
894
900
|
if (yield* _(hasNonEmptyFile(fs, `${accountPath}/.oauth-token`))) return true;
|
|
895
|
-
if (yield* _(isRegularFile
|
|
896
|
-
if (yield* _(isRegularFile
|
|
901
|
+
if (yield* _(isRegularFile(fs, `${accountPath}/.credentials.json`))) return true;
|
|
902
|
+
if (yield* _(isRegularFile(fs, `${accountPath}/.claude/.credentials.json`))) return true;
|
|
897
903
|
}
|
|
898
904
|
return false;
|
|
899
905
|
});
|
|
@@ -1357,6 +1363,9 @@ var TemplateConfigSchema = Schema.Struct({
|
|
|
1357
1363
|
codexAuthPath: Schema.String,
|
|
1358
1364
|
codexSharedAuthPath: Schema.optionalWith(Schema.String, { default: () => defaultTemplateConfig.codexSharedAuthPath }),
|
|
1359
1365
|
codexHome: Schema.String,
|
|
1366
|
+
geminiAuthLabel: Schema.optional(Schema.String),
|
|
1367
|
+
geminiAuthPath: Schema.optionalWith(Schema.String, { default: () => defaultTemplateConfig.geminiAuthPath }),
|
|
1368
|
+
geminiHome: Schema.optionalWith(Schema.String, { default: () => defaultTemplateConfig.geminiHome }),
|
|
1360
1369
|
cpuLimit: Schema.optionalWith(Schema.String, { default: () => defaultTemplateConfig.cpuLimit }),
|
|
1361
1370
|
ramLimit: Schema.optionalWith(Schema.String, { default: () => defaultTemplateConfig.ramLimit }),
|
|
1362
1371
|
dockerNetworkMode: Schema.optionalWith(Schema.Literal("shared", "project"), { default: () => defaultTemplateConfig.dockerNetworkMode }),
|
|
@@ -2169,6 +2178,7 @@ elif [[ "$TARGET_DIR" == "~/"* ]]; then
|
|
|
2169
2178
|
fi
|
|
2170
2179
|
CLAUDE_AUTH_LABEL="\${CLAUDE_AUTH_LABEL:-}"
|
|
2171
2180
|
CODEX_AUTH_LABEL="\${CODEX_AUTH_LABEL:-}"
|
|
2181
|
+
GEMINI_AUTH_LABEL="\${GEMINI_AUTH_LABEL:-}"
|
|
2172
2182
|
GIT_AUTH_USER="\${GIT_AUTH_USER:-\${GITHUB_USER:-x-access-token}}"
|
|
2173
2183
|
GIT_AUTH_TOKEN="\${GIT_AUTH_TOKEN:-\${GITHUB_TOKEN:-\${GH_TOKEN:-}}}"
|
|
2174
2184
|
GH_TOKEN="\${GH_TOKEN:-\${GIT_AUTH_TOKEN:-}}"
|
|
@@ -2969,6 +2979,209 @@ if [[ -f "$LEGACY_AGENTS_PATH" && -f "$AGENTS_PATH" ]]; then
|
|
|
2969
2979
|
fi`;
|
|
2970
2980
|
var renderEntrypointAgentsNotice = (config) => entrypointAgentsNoticeTemplate.replaceAll("__CODEX_HOME__", config.codexHome).replaceAll("__SSH_USER__", config.sshUser).replaceAll("__TARGET_DIR__", config.targetDir);
|
|
2971
2981
|
//#endregion
|
|
2982
|
+
//#region ../lib/src/core/templates-entrypoint/gemini.ts
|
|
2983
|
+
var geminiAuthRootContainerPath = (sshUser) => `/home/${sshUser}/.docker-git/.orch/auth/gemini`;
|
|
2984
|
+
var geminiAuthConfigTemplate = String.raw`# Gemini CLI: expose GEMINI_HOME for sessions (OAuth cache lives under ~/.docker-git/.orch/auth/gemini)
|
|
2985
|
+
GEMINI_LABEL_RAW="$GEMINI_AUTH_LABEL"
|
|
2986
|
+
if [[ -z "$GEMINI_LABEL_RAW" ]]; then
|
|
2987
|
+
GEMINI_LABEL_RAW="default"
|
|
2988
|
+
fi
|
|
2989
|
+
|
|
2990
|
+
GEMINI_LABEL_NORM="$(printf "%s" "$GEMINI_LABEL_RAW" \
|
|
2991
|
+
| tr '[:upper:]' '[:lower:]' \
|
|
2992
|
+
| sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//')"
|
|
2993
|
+
if [[ -z "$GEMINI_LABEL_NORM" ]]; then
|
|
2994
|
+
GEMINI_LABEL_NORM="default"
|
|
2995
|
+
fi
|
|
2996
|
+
|
|
2997
|
+
GEMINI_AUTH_ROOT="__GEMINI_AUTH_ROOT__"
|
|
2998
|
+
export GEMINI_CONFIG_DIR="$GEMINI_AUTH_ROOT/$GEMINI_LABEL_NORM"
|
|
2999
|
+
|
|
3000
|
+
mkdir -p "$GEMINI_CONFIG_DIR" || true
|
|
3001
|
+
GEMINI_HOME_DIR="__GEMINI_HOME_DIR__"
|
|
3002
|
+
mkdir -p "$GEMINI_HOME_DIR" || true
|
|
3003
|
+
|
|
3004
|
+
docker_git_link_gemini_file() {
|
|
3005
|
+
local source_path="$1"
|
|
3006
|
+
local link_path="$2"
|
|
3007
|
+
|
|
3008
|
+
if [[ -e "$link_path" && ! -L "$link_path" ]]; then
|
|
3009
|
+
if [[ -f "$link_path" && ! -e "$source_path" ]]; then
|
|
3010
|
+
cp "$link_path" "$source_path" || true
|
|
3011
|
+
chmod 0600 "$source_path" || true
|
|
3012
|
+
fi
|
|
3013
|
+
return 0
|
|
3014
|
+
fi
|
|
3015
|
+
|
|
3016
|
+
ln -sfn "$source_path" "$link_path" || true
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
# Link .api-key and .env from central auth storage to container home
|
|
3020
|
+
docker_git_link_gemini_file "$GEMINI_CONFIG_DIR/.api-key" "$GEMINI_HOME_DIR/.api-key"
|
|
3021
|
+
docker_git_link_gemini_file "$GEMINI_CONFIG_DIR/.env" "$GEMINI_HOME_DIR/.env"
|
|
3022
|
+
|
|
3023
|
+
# Ensure gemini YOLO wrapper exists
|
|
3024
|
+
GEMINI_REAL_BIN="$(command -v gemini || echo "/usr/local/bin/gemini")"
|
|
3025
|
+
GEMINI_WRAPPER_BIN="/usr/local/bin/gemini-wrapper"
|
|
3026
|
+
if [[ -f "$GEMINI_REAL_BIN" && "$GEMINI_REAL_BIN" != "$GEMINI_WRAPPER_BIN" ]]; then
|
|
3027
|
+
if [[ ! -f "$GEMINI_WRAPPER_BIN" ]]; then
|
|
3028
|
+
cat <<'EOF' > "$GEMINI_WRAPPER_BIN"
|
|
3029
|
+
#!/usr/bin/env bash
|
|
3030
|
+
GEMINI_ORIGINAL_BIN="__GEMINI_REAL_BIN__"
|
|
3031
|
+
exec "$GEMINI_ORIGINAL_BIN" --yolo "$@"
|
|
3032
|
+
EOF
|
|
3033
|
+
sed -i "s#__GEMINI_REAL_BIN__#$GEMINI_REAL_BIN#g" "$GEMINI_WRAPPER_BIN" || true
|
|
3034
|
+
chmod 0755 "$GEMINI_WRAPPER_BIN" || true
|
|
3035
|
+
# Create an alias or symlink if needed, but here we just ensure it exists
|
|
3036
|
+
fi
|
|
3037
|
+
fi
|
|
3038
|
+
|
|
3039
|
+
# Special case for .gemini folder: we want the folder itself to be the link if it doesn't exist
|
|
3040
|
+
# or its content to be linked if we want to manage it.
|
|
3041
|
+
if [[ -d "$GEMINI_CONFIG_DIR/.gemini" ]]; then
|
|
3042
|
+
if [[ -L "$GEMINI_HOME_DIR" ]]; then
|
|
3043
|
+
rm -f "$GEMINI_HOME_DIR"
|
|
3044
|
+
elif [[ -d "$GEMINI_HOME_DIR" ]]; then
|
|
3045
|
+
# If it's a real directory, move it aside if it's empty or just has our managed files
|
|
3046
|
+
mv "$GEMINI_HOME_DIR" "$GEMINI_HOME_DIR.bak-$(date +%s)" || true
|
|
3047
|
+
fi
|
|
3048
|
+
ln -sfn "$GEMINI_CONFIG_DIR/.gemini" "$GEMINI_HOME_DIR"
|
|
3049
|
+
fi
|
|
3050
|
+
|
|
3051
|
+
docker_git_refresh_gemini_env() {
|
|
3052
|
+
# If .api-key exists, export it as GEMINI_API_KEY
|
|
3053
|
+
if [[ -f "$GEMINI_HOME_DIR/.api-key" ]]; then
|
|
3054
|
+
export GEMINI_API_KEY="$(cat "$GEMINI_HOME_DIR/.api-key" | tr -d '\r\n')"
|
|
3055
|
+
elif [[ -f "$GEMINI_HOME_DIR/.env" ]]; then
|
|
3056
|
+
# Parse GEMINI_API_KEY from .env
|
|
3057
|
+
API_KEY="$(grep "^GEMINI_API_KEY=" "$GEMINI_HOME_DIR/.env" | cut -d'=' -f2- | sed "s/^['\"]//;s/['\"]$//")"
|
|
3058
|
+
if [[ -n "$API_KEY" ]]; then
|
|
3059
|
+
export GEMINI_API_KEY="$API_KEY"
|
|
3060
|
+
fi
|
|
3061
|
+
fi
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
docker_git_refresh_gemini_env`;
|
|
3065
|
+
var renderGeminiAuthConfig = (config) => geminiAuthConfigTemplate.replaceAll("__GEMINI_AUTH_ROOT__", geminiAuthRootContainerPath(config.sshUser)).replaceAll("__GEMINI_HOME_DIR__", config.geminiHome);
|
|
3066
|
+
var renderGeminiPermissionSettingsConfig = (config) => String.raw`# Gemini CLI: keep trust settings in sync with docker-git defaults
|
|
3067
|
+
GEMINI_SETTINGS_DIR="${config.geminiHome}"
|
|
3068
|
+
GEMINI_TRUST_SETTINGS_FILE="$GEMINI_SETTINGS_DIR/trustedFolders.json"
|
|
3069
|
+
GEMINI_CONFIG_SETTINGS_FILE="$GEMINI_SETTINGS_DIR/settings.json"
|
|
3070
|
+
|
|
3071
|
+
# Wait for symlink to be established by the auth config step
|
|
3072
|
+
mkdir -p "$GEMINI_SETTINGS_DIR" || true
|
|
3073
|
+
|
|
3074
|
+
# Disable folder trust prompt and enable auto-approval in settings.json
|
|
3075
|
+
if [[ ! -f "$GEMINI_CONFIG_SETTINGS_FILE" ]]; then
|
|
3076
|
+
cat <<'EOF' > "$GEMINI_CONFIG_SETTINGS_FILE"
|
|
3077
|
+
{
|
|
3078
|
+
"security": {
|
|
3079
|
+
"folderTrust": {
|
|
3080
|
+
"enabled": false
|
|
3081
|
+
},
|
|
3082
|
+
"approvalPolicy": "never"
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
EOF
|
|
3086
|
+
fi
|
|
3087
|
+
|
|
3088
|
+
# Pre-trust important directories in trustedFolders.json
|
|
3089
|
+
# Use flat mapping as required by recent Gemini CLI versions
|
|
3090
|
+
cat <<'EOF' > "$GEMINI_TRUST_SETTINGS_FILE"
|
|
3091
|
+
{
|
|
3092
|
+
"/": "TRUST_FOLDER",
|
|
3093
|
+
"${config.geminiHome}": "TRUST_FOLDER",
|
|
3094
|
+
"${config.targetDir}": "TRUST_FOLDER"
|
|
3095
|
+
}
|
|
3096
|
+
EOF
|
|
3097
|
+
|
|
3098
|
+
chown -R 1000:1000 "$GEMINI_SETTINGS_DIR" || true
|
|
3099
|
+
chmod 0600 "$GEMINI_TRUST_SETTINGS_FILE" "$GEMINI_CONFIG_SETTINGS_FILE" 2>/dev/null || true`;
|
|
3100
|
+
var renderGeminiSudoConfig = (config) => String.raw`# Gemini CLI: allow passwordless sudo for agent tasks
|
|
3101
|
+
if [[ -d /etc/sudoers.d ]]; then
|
|
3102
|
+
echo "${config.sshUser} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/gemini-agent
|
|
3103
|
+
chmod 0440 /etc/sudoers.d/gemini-agent
|
|
3104
|
+
fi`;
|
|
3105
|
+
var renderGeminiMcpPlaywrightConfig = (_config) => String.raw`# Gemini CLI: keep Playwright MCP config in sync (TODO: Gemini CLI MCP integration format)
|
|
3106
|
+
# For now, Gemini CLI uses MCP via ~/.gemini/settings.json or command line.
|
|
3107
|
+
# We'll ensure it has the same Playwright capability as Claude/Codex once format is confirmed.`;
|
|
3108
|
+
var renderGeminiProfileSetup = (config) => String.raw`GEMINI_PROFILE="/etc/profile.d/gemini-config.sh"
|
|
3109
|
+
printf "export GEMINI_AUTH_LABEL=%q\n" "$GEMINI_AUTH_LABEL" > "$GEMINI_PROFILE"
|
|
3110
|
+
printf "export GEMINI_HOME=%q\n" "${config.geminiHome}" >> "$GEMINI_PROFILE"
|
|
3111
|
+
printf "export GEMINI_CLI_DISABLE_UPDATE_CHECK=true\n" >> "$GEMINI_PROFILE"
|
|
3112
|
+
printf "export GEMINI_CLI_NONINTERACTIVE=true\n" >> "$GEMINI_PROFILE"
|
|
3113
|
+
printf "export GEMINI_CLI_APPROVAL_MODE=yolo\n" >> "$GEMINI_PROFILE"
|
|
3114
|
+
printf "alias gemini='/usr/local/bin/gemini-wrapper'\n" >> "$GEMINI_PROFILE"
|
|
3115
|
+
cat <<'EOF' >> "$GEMINI_PROFILE"
|
|
3116
|
+
if [[ -f "$GEMINI_HOME/.api-key" ]]; then
|
|
3117
|
+
export GEMINI_API_KEY="$(cat "$GEMINI_HOME/.api-key" | tr -d '\r\n')"
|
|
3118
|
+
fi
|
|
3119
|
+
EOF
|
|
3120
|
+
chmod 0644 "$GEMINI_PROFILE" || true
|
|
3121
|
+
|
|
3122
|
+
docker_git_upsert_ssh_env "GEMINI_AUTH_LABEL" "$GEMINI_AUTH_LABEL"
|
|
3123
|
+
docker_git_upsert_ssh_env "GEMINI_API_KEY" "\${GEMINI_API_KEY:-}"
|
|
3124
|
+
docker_git_upsert_ssh_env "GEMINI_CLI_DISABLE_UPDATE_CHECK" "true"
|
|
3125
|
+
docker_git_upsert_ssh_env "GEMINI_CLI_NONINTERACTIVE" "true"
|
|
3126
|
+
docker_git_upsert_ssh_env "GEMINI_CLI_APPROVAL_MODE" "yolo"`;
|
|
3127
|
+
var entrypointGeminiNoticeTemplate = String.raw`# Ensure global GEMINI.md exists for container context
|
|
3128
|
+
GEMINI_MD_PATH="__GEMINI_HOME__/GEMINI.md"
|
|
3129
|
+
GEMINI_WORKSPACE_CONTEXT="Контекст workspace: repository"
|
|
3130
|
+
if [[ "$REPO_REF" == issue-* ]]; then
|
|
3131
|
+
ISSUE_ID="$(printf "%s" "$REPO_REF" | sed -E 's#^issue-##')"
|
|
3132
|
+
ISSUE_URL=""
|
|
3133
|
+
if [[ "$REPO_URL" == https://github.com/* ]]; then
|
|
3134
|
+
ISSUE_REPO="$(printf "%s" "$REPO_URL" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')"
|
|
3135
|
+
if [[ -n "$ISSUE_REPO" ]]; then
|
|
3136
|
+
ISSUE_URL="https://github.com/$ISSUE_REPO/issues/$ISSUE_ID"
|
|
3137
|
+
fi
|
|
3138
|
+
fi
|
|
3139
|
+
if [[ -n "$ISSUE_URL" ]]; then
|
|
3140
|
+
GEMINI_WORKSPACE_CONTEXT="Контекст workspace: issue #$ISSUE_ID ($ISSUE_URL)"
|
|
3141
|
+
else
|
|
3142
|
+
GEMINI_WORKSPACE_CONTEXT="Контекст workspace: issue #$ISSUE_ID"
|
|
3143
|
+
fi
|
|
3144
|
+
elif [[ "$REPO_REF" == refs/pull/*/head ]]; then
|
|
3145
|
+
PR_ID="$(printf "%s" "$REPO_REF" | sed -nE 's#^refs/pull/([0-9]+)/head$#\1#p')"
|
|
3146
|
+
PR_URL=""
|
|
3147
|
+
if [[ "$REPO_URL" == https://github.com/* && -n "$PR_ID" ]]; then
|
|
3148
|
+
PR_REPO="$(printf "%s" "$REPO_URL" | sed -E 's#^https://github.com/##; s#[.]git$##; s#/*$##')"
|
|
3149
|
+
if [[ -n "$PR_REPO" ]]; then
|
|
3150
|
+
PR_URL="https://github.com/$PR_REPO/pull/$PR_ID"
|
|
3151
|
+
fi
|
|
3152
|
+
fi
|
|
3153
|
+
if [[ -n "$PR_ID" && -n "$PR_URL" ]]; then
|
|
3154
|
+
GEMINI_WORKSPACE_CONTEXT="Контекст workspace: PR #$PR_ID ($PR_URL)"
|
|
3155
|
+
elif [[ -n "$PR_ID" ]]; then
|
|
3156
|
+
GEMINI_WORKSPACE_CONTEXT="Контекст workspace: PR #$PR_ID"
|
|
3157
|
+
else
|
|
3158
|
+
GEMINI_WORKSPACE_CONTEXT="Контекст workspace: pull request ($REPO_REF)"
|
|
3159
|
+
fi
|
|
3160
|
+
fi
|
|
3161
|
+
|
|
3162
|
+
cat <<EOF > "$GEMINI_MD_PATH"
|
|
3163
|
+
<!-- docker-git-managed:gemini-md -->
|
|
3164
|
+
Ты автономный агент, который имеет полностью все права управления контейнером. У тебя есть доступ к командам sudo, gh, codex, gemini, claude, opencode, oh-my-opencode, sshpass, git, node, pnpm и всем остальным другим. Проекты с которыми идёт работа лежат по пути ~
|
|
3165
|
+
Рабочая папка проекта (git clone): __TARGET_DIR__
|
|
3166
|
+
Доступные workspace пути: __TARGET_DIR__
|
|
3167
|
+
$GEMINI_WORKSPACE_CONTEXT
|
|
3168
|
+
Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__
|
|
3169
|
+
Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе.
|
|
3170
|
+
Для решения задач обязательно используй subagents. Сам агент обязан выполнять финальную проверку, интеграцию и валидацию результата перед ответом пользователю.
|
|
3171
|
+
Если ты видишь файлы AGENTS.md, GEMINI.md или CLAUDE.md внутри проекта, ты обязан их читать и соблюдать инструкции.
|
|
3172
|
+
<!-- /docker-git-managed:gemini-md -->
|
|
3173
|
+
EOF
|
|
3174
|
+
chown 1000:1000 "$GEMINI_MD_PATH" || true`;
|
|
3175
|
+
var renderEntrypointGeminiNotice = (config) => entrypointGeminiNoticeTemplate.replaceAll("__GEMINI_HOME__", config.geminiHome).replaceAll("__TARGET_DIR__", config.targetDir);
|
|
3176
|
+
var renderEntrypointGeminiConfig = (config) => [
|
|
3177
|
+
renderGeminiAuthConfig(config),
|
|
3178
|
+
renderGeminiPermissionSettingsConfig(config),
|
|
3179
|
+
renderGeminiMcpPlaywrightConfig(config),
|
|
3180
|
+
renderGeminiSudoConfig(config),
|
|
3181
|
+
renderGeminiProfileSetup(config),
|
|
3182
|
+
renderEntrypointGeminiNotice(config)
|
|
3183
|
+
].join("\n\n");
|
|
3184
|
+
//#endregion
|
|
2972
3185
|
//#region ../lib/src/core/templates-entrypoint/git.ts
|
|
2973
3186
|
var renderAuthLabelResolution = () => String.raw`# 2) Ensure GitHub auth vars are available for SSH sessions.
|
|
2974
3187
|
# Prefer a label-selected token (same selection model as clone/create) when present.
|
|
@@ -3534,6 +3747,7 @@ AGENT_ENV_FILE="/run/docker-git/agent-env.sh"
|
|
|
3534
3747
|
{
|
|
3535
3748
|
[[ -f /etc/profile.d/gh-token.sh ]] && cat /etc/profile.d/gh-token.sh
|
|
3536
3749
|
[[ -f /etc/profile.d/claude-config.sh ]] && cat /etc/profile.d/claude-config.sh
|
|
3750
|
+
[[ -f /etc/profile.d/gemini-config.sh ]] && cat /etc/profile.d/gemini-config.sh
|
|
3537
3751
|
} > "$AGENT_ENV_FILE" 2>/dev/null || true
|
|
3538
3752
|
chmod 644 "$AGENT_ENV_FILE"`,
|
|
3539
3753
|
renderAgentPrompt(),
|
|
@@ -3543,7 +3757,7 @@ if [[ -n "$AGENT_PROMPT" ]]; then
|
|
|
3543
3757
|
chmod 644 "$AGENT_PROMPT_FILE"
|
|
3544
3758
|
fi`
|
|
3545
3759
|
].join("\n\n");
|
|
3546
|
-
var renderAgentPromptCommand = (mode) => mode
|
|
3760
|
+
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);
|
|
3547
3761
|
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)}'"`;
|
|
3548
3762
|
var renderAgentModeBlock = (config, mode) => {
|
|
3549
3763
|
const startMessage = `[agent] starting ${mode}...`;
|
|
@@ -3564,6 +3778,7 @@ var renderAgentModeCase = (config) => [
|
|
|
3564
3778
|
String.raw`case "$AGENT_MODE" in`,
|
|
3565
3779
|
indentBlock(renderAgentModeBlock(config, "claude")),
|
|
3566
3780
|
indentBlock(renderAgentModeBlock(config, "codex")),
|
|
3781
|
+
indentBlock(renderAgentModeBlock(config, "gemini")),
|
|
3567
3782
|
indentBlock(String.raw`*)
|
|
3568
3783
|
echo "[agent] unknown agent mode: $AGENT_MODE"
|
|
3569
3784
|
;;`),
|
|
@@ -3871,6 +4086,7 @@ var renderEntrypoint = (config) => [
|
|
|
3871
4086
|
renderEntrypointDockerSocket(config),
|
|
3872
4087
|
renderEntrypointGitConfig(config),
|
|
3873
4088
|
renderEntrypointClaudeConfig(config),
|
|
4089
|
+
renderEntrypointGeminiConfig(config),
|
|
3874
4090
|
renderEntrypointGitHooks(),
|
|
3875
4091
|
renderEntrypointBackgroundTasks(config),
|
|
3876
4092
|
renderEntrypointBaseline(),
|
|
@@ -4035,7 +4251,9 @@ RUN set -eu; \
|
|
|
4035
4251
|
npm install -g oh-my-opencode@latest "oh-my-opencode-linux-\${OH_MY_OPENCODE_ARCH}@latest"
|
|
4036
4252
|
RUN oh-my-opencode --version
|
|
4037
4253
|
RUN npm install -g @anthropic-ai/claude-code@latest
|
|
4038
|
-
RUN claude --version
|
|
4254
|
+
RUN claude --version
|
|
4255
|
+
RUN npm install -g @google/gemini-cli@latest --force
|
|
4256
|
+
RUN gemini --version`;
|
|
4039
4257
|
var renderDockerfileOpenCode = () => `# Tooling: OpenCode (binary)
|
|
4040
4258
|
RUN set -eu; \
|
|
4041
4259
|
for attempt in 1 2 3 4 5; do \
|
|
@@ -4427,7 +4645,7 @@ var resolveOriginPushTarget = (originUrl) => {
|
|
|
4427
4645
|
const trimmed = originUrl?.trim() ?? "";
|
|
4428
4646
|
return trimmed.length > 0 ? trimmed : "origin";
|
|
4429
4647
|
};
|
|
4430
|
-
var resolveSyncMessage = (value) => {
|
|
4648
|
+
var resolveSyncMessage$1 = (value) => {
|
|
4431
4649
|
const trimmed = value?.trim() ?? "";
|
|
4432
4650
|
return trimmed.length > 0 ? trimmed : defaultSyncMessage;
|
|
4433
4651
|
};
|
|
@@ -4508,7 +4726,7 @@ var runStateSyncOps = (root, originUrl, message, env, options) => Effect.gen(fun
|
|
|
4508
4726
|
yield* _(normalizeLegacyStateProjects(root));
|
|
4509
4727
|
const baseBranch = resolveBaseBranch(yield* _(getCurrentBranch(root, env)));
|
|
4510
4728
|
yield* _(pullRemoteAndRestoreLocal(root, baseBranch, env));
|
|
4511
|
-
yield* _(commitAllIfNeeded(root, resolveSyncMessage(message), env));
|
|
4729
|
+
yield* _(commitAllIfNeeded(root, resolveSyncMessage$1(message), env));
|
|
4512
4730
|
const pushExit = yield* _(gitExitCode(root, [
|
|
4513
4731
|
"push",
|
|
4514
4732
|
"--no-verify",
|
|
@@ -5921,12 +6139,7 @@ var applyProjectConfig = (command) => runApplyForProjectDir(command.projectDir,
|
|
|
5921
6139
|
return yield* _(runApplyForProjectDir(inferredProjectDir, command));
|
|
5922
6140
|
}) : Effect.fail(error)));
|
|
5923
6141
|
//#endregion
|
|
5924
|
-
//#region ../lib/src/
|
|
5925
|
-
var oauthTokenEnvKey = "DOCKER_GIT_CLAUDE_OAUTH_TOKEN";
|
|
5926
|
-
var tokenMarker = "Your OAuth token (valid for 1 year):";
|
|
5927
|
-
var tokenFooterMarker = "Store this token securely.";
|
|
5928
|
-
var outputWindowSize = 262144;
|
|
5929
|
-
var oauthTokenRegex = /([A-Za-z0-9][A-Za-z0-9._-]{20,})/u;
|
|
6142
|
+
//#region ../lib/src/shell/ansi-strip.ts
|
|
5930
6143
|
var ansiEscape = "\x1B";
|
|
5931
6144
|
var ansiBell = "\x07";
|
|
5932
6145
|
var isAnsiFinalByte = (codePoint) => codePoint !== void 0 && codePoint >= 64 && codePoint <= 126;
|
|
@@ -5970,6 +6183,20 @@ var stripAnsi = (raw) => {
|
|
|
5970
6183
|
}
|
|
5971
6184
|
return cleaned.join("");
|
|
5972
6185
|
};
|
|
6186
|
+
var writeChunkToFd = (fd, chunk) => {
|
|
6187
|
+
if (fd === 2) {
|
|
6188
|
+
process.stderr.write(chunk);
|
|
6189
|
+
return;
|
|
6190
|
+
}
|
|
6191
|
+
process.stdout.write(chunk);
|
|
6192
|
+
};
|
|
6193
|
+
//#endregion
|
|
6194
|
+
//#region ../lib/src/usecases/auth-claude-oauth.ts
|
|
6195
|
+
var oauthTokenEnvKey = "DOCKER_GIT_CLAUDE_OAUTH_TOKEN";
|
|
6196
|
+
var tokenMarker = "Your OAuth token (valid for 1 year):";
|
|
6197
|
+
var tokenFooterMarker = "Store this token securely.";
|
|
6198
|
+
var outputWindowSize$1 = 262144;
|
|
6199
|
+
var oauthTokenRegex = /([A-Za-z0-9][A-Za-z0-9._-]{20,})/u;
|
|
5973
6200
|
var extractOauthToken = (rawOutput) => {
|
|
5974
6201
|
const normalized = stripAnsi(rawOutput).replaceAll("\r", "\n");
|
|
5975
6202
|
const markerIndex = normalized.lastIndexOf(tokenMarker);
|
|
@@ -6024,21 +6251,14 @@ var buildDockerSetupTokenArgs = (spec) => {
|
|
|
6024
6251
|
...spec.args
|
|
6025
6252
|
];
|
|
6026
6253
|
};
|
|
6027
|
-
var startDockerProcess = (executor, spec) => executor.start(pipe(Command.make("docker", ...buildDockerSetupTokenArgs(spec)), Command.workingDirectory(spec.cwd), Command.stdin("inherit"), Command.stdout("pipe"), Command.stderr("pipe")));
|
|
6028
|
-
var
|
|
6029
|
-
if (fd === 2) {
|
|
6030
|
-
process.stderr.write(chunk);
|
|
6031
|
-
return;
|
|
6032
|
-
}
|
|
6033
|
-
process.stdout.write(chunk);
|
|
6034
|
-
};
|
|
6035
|
-
var pumpDockerOutput = (source, fd, tokenBox) => {
|
|
6254
|
+
var startDockerProcess$1 = (executor, spec) => executor.start(pipe(Command.make("docker", ...buildDockerSetupTokenArgs(spec)), Command.workingDirectory(spec.cwd), Command.stdin("inherit"), Command.stdout("pipe"), Command.stderr("pipe")));
|
|
6255
|
+
var pumpDockerOutput$1 = (source, fd, tokenBox) => {
|
|
6036
6256
|
const decoder = new TextDecoder("utf-8");
|
|
6037
6257
|
let outputWindow = "";
|
|
6038
6258
|
return pipe(source, Stream.runForEach((chunk) => Effect.sync(() => {
|
|
6039
6259
|
writeChunkToFd(fd, chunk);
|
|
6040
6260
|
outputWindow += decoder.decode(chunk);
|
|
6041
|
-
if (outputWindow.length > outputWindowSize) outputWindow = outputWindow.slice(-outputWindowSize);
|
|
6261
|
+
if (outputWindow.length > outputWindowSize$1) outputWindow = outputWindow.slice(-outputWindowSize$1);
|
|
6042
6262
|
if (tokenBox.value !== null) return;
|
|
6043
6263
|
const parsed = extractOauthToken(outputWindow);
|
|
6044
6264
|
if (parsed !== null) tokenBox.value = parsed;
|
|
@@ -6060,10 +6280,10 @@ var runClaudeOauthLoginWithPrompt = (cwd, accountPath, options) => {
|
|
|
6060
6280
|
const envToken = oauthTokenFromEnv();
|
|
6061
6281
|
if (envToken !== null) return ensureOauthToken(envToken);
|
|
6062
6282
|
return Effect.scoped(Effect.gen(function* (_) {
|
|
6063
|
-
const proc = yield* _(startDockerProcess(yield* _(CommandExecutor.CommandExecutor), buildDockerSetupTokenSpec(cwd, yield* _(resolveDockerVolumeHostPath(cwd, accountPath)), options.image, options.containerPath)));
|
|
6283
|
+
const proc = yield* _(startDockerProcess$1(yield* _(CommandExecutor.CommandExecutor), buildDockerSetupTokenSpec(cwd, yield* _(resolveDockerVolumeHostPath(cwd, accountPath)), options.image, options.containerPath)));
|
|
6064
6284
|
const tokenBox = { value: null };
|
|
6065
|
-
const stdoutFiber = yield* _(Effect.forkScoped(pumpDockerOutput(proc.stdout, 1, tokenBox)));
|
|
6066
|
-
const stderrFiber = yield* _(Effect.forkScoped(pumpDockerOutput(proc.stderr, 2, tokenBox)));
|
|
6285
|
+
const stdoutFiber = yield* _(Effect.forkScoped(pumpDockerOutput$1(proc.stdout, 1, tokenBox)));
|
|
6286
|
+
const stderrFiber = yield* _(Effect.forkScoped(pumpDockerOutput$1(proc.stderr, 2, tokenBox)));
|
|
6067
6287
|
const exitCode = yield* _(proc.exitCode.pipe(Effect.map(Number)));
|
|
6068
6288
|
yield* _(Fiber$1.join(stdoutFiber));
|
|
6069
6289
|
yield* _(Fiber$1.join(stderrFiber));
|
|
@@ -6084,19 +6304,15 @@ var claudeOauthTokenPath = (accountPath) => `${accountPath}/${claudeOauthTokenFi
|
|
|
6084
6304
|
var claudeConfigPath = (accountPath) => `${accountPath}/${claudeConfigFileName}`;
|
|
6085
6305
|
var claudeCredentialsPath = (accountPath) => `${accountPath}/${claudeCredentialsFileName}`;
|
|
6086
6306
|
var claudeNestedCredentialsPath = (accountPath) => `${accountPath}/${claudeCredentialsDirName}/${claudeCredentialsFileName}`;
|
|
6087
|
-
var isRegularFile = (fs, filePath) => Effect.gen(function* (_) {
|
|
6088
|
-
if (!(yield* _(fs.exists(filePath)))) return false;
|
|
6089
|
-
return (yield* _(fs.stat(filePath))).type === "File";
|
|
6090
|
-
});
|
|
6091
6307
|
var syncClaudeCredentialsFile = (fs, accountPath) => Effect.gen(function* (_) {
|
|
6092
6308
|
const nestedPath = claudeNestedCredentialsPath(accountPath);
|
|
6093
6309
|
const rootPath = claudeCredentialsPath(accountPath);
|
|
6094
|
-
if (yield* _(isRegularFile(fs, nestedPath))) {
|
|
6310
|
+
if (yield* _(isRegularFile$1(fs, nestedPath))) {
|
|
6095
6311
|
yield* _(fs.copyFile(nestedPath, rootPath));
|
|
6096
6312
|
yield* _(fs.chmod(rootPath, 384), Effect.orElseSucceed(() => void 0));
|
|
6097
6313
|
return;
|
|
6098
6314
|
}
|
|
6099
|
-
if (yield* _(isRegularFile(fs, rootPath))) {
|
|
6315
|
+
if (yield* _(isRegularFile$1(fs, rootPath))) {
|
|
6100
6316
|
const nestedDirPath = `${accountPath}/${claudeCredentialsDirName}`;
|
|
6101
6317
|
yield* _(fs.makeDirectory(nestedDirPath, { recursive: true }));
|
|
6102
6318
|
yield* _(fs.copyFile(rootPath, nestedPath));
|
|
@@ -6109,12 +6325,12 @@ var clearClaudeSessionCredentials = (fs, accountPath) => Effect.gen(function* (_
|
|
|
6109
6325
|
});
|
|
6110
6326
|
var hasNonEmptyOauthToken$1 = (fs, accountPath) => Effect.gen(function* (_) {
|
|
6111
6327
|
const tokenPath = claudeOauthTokenPath(accountPath);
|
|
6112
|
-
if (!(yield* _(isRegularFile(fs, tokenPath)))) return false;
|
|
6328
|
+
if (!(yield* _(isRegularFile$1(fs, tokenPath)))) return false;
|
|
6113
6329
|
return (yield* _(fs.readFileString(tokenPath), Effect.orElseSucceed(() => ""))).trim().length > 0;
|
|
6114
6330
|
});
|
|
6115
6331
|
var readOauthToken = (fs, accountPath) => Effect.gen(function* (_) {
|
|
6116
6332
|
const tokenPath = claudeOauthTokenPath(accountPath);
|
|
6117
|
-
if (!(yield* _(isRegularFile(fs, tokenPath)))) return null;
|
|
6333
|
+
if (!(yield* _(isRegularFile$1(fs, tokenPath)))) return null;
|
|
6118
6334
|
const token = (yield* _(fs.readFileString(tokenPath), Effect.orElseSucceed(() => ""))).trim();
|
|
6119
6335
|
return token.length > 0 ? token : null;
|
|
6120
6336
|
});
|
|
@@ -6124,7 +6340,7 @@ var resolveClaudeAuthMethod = (fs, accountPath) => Effect.gen(function* (_) {
|
|
|
6124
6340
|
return "oauth-token";
|
|
6125
6341
|
}
|
|
6126
6342
|
yield* _(syncClaudeCredentialsFile(fs, accountPath));
|
|
6127
|
-
return (yield* _(isRegularFile(fs, claudeCredentialsPath(accountPath)))) ? "claude-ai-session" : "none";
|
|
6343
|
+
return (yield* _(isRegularFile$1(fs, claudeCredentialsPath(accountPath)))) ? "claude-ai-session" : "none";
|
|
6128
6344
|
});
|
|
6129
6345
|
var buildClaudeAuthEnv = (interactive, oauthToken = null) => [...interactive ? [
|
|
6130
6346
|
`HOME=${claudeContainerHomeDir}`,
|
|
@@ -6349,6 +6565,353 @@ var authCodexStatus = (command) => withCodexAuth(command, ({ accountPath, cwd })
|
|
|
6349
6565
|
}));
|
|
6350
6566
|
var authCodexLogout = (command) => withCodexAuth(command, ({ accountPath, cwd }) => runCodexLogout(cwd, accountPath)).pipe(Effect.zipRight(autoSyncState(`chore(state): auth codex logout ${normalizeAccountLabel(command.label, "default")}`)));
|
|
6351
6567
|
//#endregion
|
|
6568
|
+
//#region ../lib/src/usecases/auth-gemini-oauth.ts
|
|
6569
|
+
var outputWindowSize = 262144;
|
|
6570
|
+
var authSuccessPatterns = [
|
|
6571
|
+
"Authentication succeeded",
|
|
6572
|
+
"Authentication successful",
|
|
6573
|
+
"Successfully authenticated",
|
|
6574
|
+
"Logged in as",
|
|
6575
|
+
"You are now logged in",
|
|
6576
|
+
"Logged in with Google"
|
|
6577
|
+
];
|
|
6578
|
+
var authFailurePatterns = [
|
|
6579
|
+
"Authentication failed",
|
|
6580
|
+
"Failed to authenticate",
|
|
6581
|
+
"Authorization failed",
|
|
6582
|
+
"Authentication timed out",
|
|
6583
|
+
"Authentication cancelled"
|
|
6584
|
+
];
|
|
6585
|
+
var detectAuthResult = (output) => {
|
|
6586
|
+
const normalized = stripAnsi(output).toLowerCase();
|
|
6587
|
+
const authInitiated = [
|
|
6588
|
+
"please visit the following url",
|
|
6589
|
+
"enter the authorization code",
|
|
6590
|
+
"authorized the application"
|
|
6591
|
+
].some((m) => normalized.includes(m));
|
|
6592
|
+
if (authSuccessPatterns.some((pattern) => normalized.includes(pattern.toLowerCase()) && (authInitiated || normalized.includes("logged in with google")))) return "success";
|
|
6593
|
+
if (authFailurePatterns.some((pattern) => normalized.includes(pattern.toLowerCase()))) return "failure";
|
|
6594
|
+
return "pending";
|
|
6595
|
+
};
|
|
6596
|
+
var defaultGeminiOauthCallbackPort = 38751;
|
|
6597
|
+
var buildDockerGeminiAuthSpec = (cwd, accountPath, image, containerPath, port) => ({
|
|
6598
|
+
cwd,
|
|
6599
|
+
image,
|
|
6600
|
+
hostPath: accountPath,
|
|
6601
|
+
containerPath,
|
|
6602
|
+
callbackPort: port,
|
|
6603
|
+
env: [
|
|
6604
|
+
`HOME=${containerPath}`,
|
|
6605
|
+
"NO_BROWSER=true",
|
|
6606
|
+
"GEMINI_CLI_NONINTERACTIVE=true",
|
|
6607
|
+
"GEMINI_CLI_TRUST_ALL=true",
|
|
6608
|
+
`OAUTH_CALLBACK_PORT=${port}`,
|
|
6609
|
+
"OAUTH_CALLBACK_HOST=0.0.0.0"
|
|
6610
|
+
]
|
|
6611
|
+
});
|
|
6612
|
+
var buildDockerGeminiAuthArgs = (spec) => {
|
|
6613
|
+
const base = [
|
|
6614
|
+
"run",
|
|
6615
|
+
"--rm",
|
|
6616
|
+
"-i",
|
|
6617
|
+
"-t",
|
|
6618
|
+
"-v",
|
|
6619
|
+
`${spec.hostPath}:${spec.containerPath}`,
|
|
6620
|
+
"-p",
|
|
6621
|
+
`${spec.callbackPort}:${spec.callbackPort}`
|
|
6622
|
+
];
|
|
6623
|
+
for (const entry of spec.env) {
|
|
6624
|
+
const trimmed = entry.trim();
|
|
6625
|
+
if (trimmed.length === 0) continue;
|
|
6626
|
+
base.push("-e", trimmed);
|
|
6627
|
+
}
|
|
6628
|
+
return [
|
|
6629
|
+
...base,
|
|
6630
|
+
spec.image,
|
|
6631
|
+
"gemini",
|
|
6632
|
+
"login",
|
|
6633
|
+
"--debug"
|
|
6634
|
+
];
|
|
6635
|
+
};
|
|
6636
|
+
var cleanupExistingContainers = (port) => Effect.gen(function* (_) {
|
|
6637
|
+
const ids = (yield* _(runCommandCapture({
|
|
6638
|
+
cwd: process.cwd(),
|
|
6639
|
+
command: "docker",
|
|
6640
|
+
args: [
|
|
6641
|
+
"ps",
|
|
6642
|
+
"-q",
|
|
6643
|
+
"--filter",
|
|
6644
|
+
`publish=${port}`
|
|
6645
|
+
]
|
|
6646
|
+
}, [0], () => /* @__PURE__ */ new Error("docker ps failed")).pipe(Effect.map((value) => value.trim()), Effect.orElseSucceed(() => "")))).split("\n").filter(Boolean);
|
|
6647
|
+
if (ids.length > 0) {
|
|
6648
|
+
yield* _(Effect.logInfo(`Cleaning up existing containers using port ${port}: ${ids.join(", ")}`));
|
|
6649
|
+
yield* _(runCommandExitCode({
|
|
6650
|
+
cwd: process.cwd(),
|
|
6651
|
+
command: "docker",
|
|
6652
|
+
args: [
|
|
6653
|
+
"rm",
|
|
6654
|
+
"-f",
|
|
6655
|
+
...ids
|
|
6656
|
+
]
|
|
6657
|
+
}).pipe(Effect.orElse(() => Effect.succeed(0))));
|
|
6658
|
+
}
|
|
6659
|
+
});
|
|
6660
|
+
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")));
|
|
6661
|
+
var pumpDockerOutput = (source, fd, resultBox, authDeferred) => {
|
|
6662
|
+
const decoder = new TextDecoder("utf-8");
|
|
6663
|
+
let outputWindow = "";
|
|
6664
|
+
return pipe(source, Stream.runForEach((chunk) => Effect.gen(function* (_) {
|
|
6665
|
+
yield* _(Effect.sync(() => {
|
|
6666
|
+
writeChunkToFd(fd, chunk);
|
|
6667
|
+
}));
|
|
6668
|
+
outputWindow += decoder.decode(chunk);
|
|
6669
|
+
if (outputWindow.length > outputWindowSize) outputWindow = outputWindow.slice(-outputWindowSize);
|
|
6670
|
+
if (resultBox.value !== "pending") return;
|
|
6671
|
+
const result = detectAuthResult(outputWindow);
|
|
6672
|
+
if (result !== "pending") {
|
|
6673
|
+
resultBox.value = result;
|
|
6674
|
+
if (result === "success") yield* _(Deferred.succeed(authDeferred, void 0));
|
|
6675
|
+
}
|
|
6676
|
+
}))).pipe(Effect.asVoid);
|
|
6677
|
+
};
|
|
6678
|
+
var resolveGeminiLoginResult = (result, exitCode) => Effect.gen(function* (_) {
|
|
6679
|
+
if (result === "success") {
|
|
6680
|
+
if (exitCode !== 0) yield* _(Effect.logWarning(`Gemini CLI returned exit=${exitCode}, but authentication appears successful; continuing.`));
|
|
6681
|
+
return;
|
|
6682
|
+
}
|
|
6683
|
+
if (result === "failure") yield* _(Effect.fail(new AuthError({ message: "Gemini CLI OAuth authentication failed. Please try again." })));
|
|
6684
|
+
if (exitCode !== 0) yield* _(Effect.fail(new CommandFailedError({
|
|
6685
|
+
command: "gemini",
|
|
6686
|
+
exitCode
|
|
6687
|
+
})));
|
|
6688
|
+
});
|
|
6689
|
+
var printOauthInstructions = () => Effect.sync(() => {
|
|
6690
|
+
const port = defaultGeminiOauthCallbackPort;
|
|
6691
|
+
process.stderr.write("\n");
|
|
6692
|
+
process.stderr.write("╔═══════════════════════════════════════════════════════════════════════════╗\n");
|
|
6693
|
+
process.stderr.write("║ Gemini CLI OAuth Authentication ║\n");
|
|
6694
|
+
process.stderr.write("╠═══════════════════════════════════════════════════════════════════════════╣\n");
|
|
6695
|
+
process.stderr.write("║ 1. Copy the auth URL shown below and open it in your browser ║\n");
|
|
6696
|
+
process.stderr.write("║ 2. Sign in with your Google account ║\n");
|
|
6697
|
+
process.stderr.write(`║ 3. After authentication, the browser will redirect to localhost:${port} ║\n`);
|
|
6698
|
+
process.stderr.write("║ 4. The callback will be captured automatically (port is forwarded) ║\n");
|
|
6699
|
+
process.stderr.write("╚═══════════════════════════════════════════════════════════════════════════╝\n");
|
|
6700
|
+
process.stderr.write("\n");
|
|
6701
|
+
});
|
|
6702
|
+
var fixGeminiAuthPermissions = (hostPath, containerPath) => runCommandExitCode({
|
|
6703
|
+
cwd: process.cwd(),
|
|
6704
|
+
command: "docker",
|
|
6705
|
+
args: [
|
|
6706
|
+
"run",
|
|
6707
|
+
"--rm",
|
|
6708
|
+
"-v",
|
|
6709
|
+
`${hostPath}:${containerPath}`,
|
|
6710
|
+
"alpine",
|
|
6711
|
+
"chmod",
|
|
6712
|
+
"-R",
|
|
6713
|
+
"777",
|
|
6714
|
+
containerPath
|
|
6715
|
+
]
|
|
6716
|
+
}).pipe(Effect.tapError((err) => Effect.logWarning(`Failed to fix Gemini auth permissions: ${String(err)}`)), Effect.orElse(() => Effect.succeed(0)));
|
|
6717
|
+
var runGeminiOauthLoginWithPrompt = (cwd, accountPath, options) => Effect.scoped(Effect.gen(function* (_) {
|
|
6718
|
+
const port = defaultGeminiOauthCallbackPort;
|
|
6719
|
+
yield* _(cleanupExistingContainers(port));
|
|
6720
|
+
yield* _(printOauthInstructions());
|
|
6721
|
+
const executor = yield* _(CommandExecutor.CommandExecutor);
|
|
6722
|
+
const hostPath = yield* _(resolveDockerVolumeHostPath(cwd, accountPath));
|
|
6723
|
+
const spec = buildDockerGeminiAuthSpec(cwd, hostPath, options.image, options.containerPath, port);
|
|
6724
|
+
const proc = yield* _(startDockerProcess(executor, spec));
|
|
6725
|
+
const authDeferred = yield* _(Deferred.make());
|
|
6726
|
+
const resultBox = { value: "pending" };
|
|
6727
|
+
const stdoutFiber = yield* _(Effect.forkScoped(pumpDockerOutput(proc.stdout, 1, resultBox, authDeferred)));
|
|
6728
|
+
const stderrFiber = yield* _(Effect.forkScoped(pumpDockerOutput(proc.stderr, 2, resultBox, authDeferred)));
|
|
6729
|
+
const exitCode = yield* _(Effect.race(proc.exitCode.pipe(Effect.map(Number)), pipe(Deferred.await(authDeferred), Effect.flatMap(() => proc.kill()), Effect.map(() => 0))));
|
|
6730
|
+
yield* _(Fiber$1.join(stdoutFiber));
|
|
6731
|
+
yield* _(Fiber$1.join(stderrFiber));
|
|
6732
|
+
yield* _(fixGeminiAuthPermissions(hostPath, spec.containerPath));
|
|
6733
|
+
return yield* _(resolveGeminiLoginResult(resultBox.value, exitCode));
|
|
6734
|
+
}));
|
|
6735
|
+
//#endregion
|
|
6736
|
+
//#region ../lib/src/usecases/auth-gemini.ts
|
|
6737
|
+
var geminiImageName = "docker-git-auth-gemini:latest";
|
|
6738
|
+
var geminiImageDir = ".docker-git/.orch/auth/gemini/.image";
|
|
6739
|
+
var geminiContainerHomeDir = "/gemini-home";
|
|
6740
|
+
var geminiCredentialsDir$1 = ".gemini";
|
|
6741
|
+
var geminiAuthRoot = ".docker-git/.orch/auth/gemini";
|
|
6742
|
+
var geminiApiKeyFileName = ".api-key";
|
|
6743
|
+
var geminiEnvFileName = ".env";
|
|
6744
|
+
var geminiApiKeyPath = (accountPath) => `${accountPath}/${geminiApiKeyFileName}`;
|
|
6745
|
+
var geminiEnvFilePath = (accountPath) => `${accountPath}/${geminiEnvFileName}`;
|
|
6746
|
+
var geminiCredentialsPath = (accountPath) => `${accountPath}/${geminiCredentialsDir$1}`;
|
|
6747
|
+
var renderGeminiDockerfile = () => String.raw`FROM ubuntu:24.04
|
|
6748
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
6749
|
+
RUN apt-get update \
|
|
6750
|
+
&& apt-get install -y --no-install-recommends ca-certificates curl bsdutils \
|
|
6751
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
6752
|
+
RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
|
|
6753
|
+
&& apt-get install -y --no-install-recommends nodejs \
|
|
6754
|
+
&& node -v \
|
|
6755
|
+
&& npm -v \
|
|
6756
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
6757
|
+
RUN npm install -g @google/gemini-cli@latest
|
|
6758
|
+
ENTRYPOINT ["/bin/bash", "-c"]
|
|
6759
|
+
`;
|
|
6760
|
+
var ensureGeminiOrchLayout = (cwd) => migrateLegacyOrchLayout(cwd, {
|
|
6761
|
+
envGlobalPath: defaultTemplateConfig.envGlobalPath,
|
|
6762
|
+
envProjectPath: defaultTemplateConfig.envProjectPath,
|
|
6763
|
+
codexAuthPath: defaultTemplateConfig.codexAuthPath,
|
|
6764
|
+
ghAuthPath: ".docker-git/.orch/auth/gh",
|
|
6765
|
+
claudeAuthPath: ".docker-git/.orch/auth/claude",
|
|
6766
|
+
geminiAuthPath: ".docker-git/.orch/auth/gemini"
|
|
6767
|
+
});
|
|
6768
|
+
var resolveGeminiAccountPath = (path, rootPath, label) => {
|
|
6769
|
+
const accountLabel = normalizeAccountLabel(label, "default");
|
|
6770
|
+
return {
|
|
6771
|
+
accountLabel,
|
|
6772
|
+
accountPath: path.join(rootPath, accountLabel)
|
|
6773
|
+
};
|
|
6774
|
+
};
|
|
6775
|
+
var withGeminiAuth = (command, run, options = {}) => withFsPathContext(({ cwd, fs, path }) => Effect.gen(function* (_) {
|
|
6776
|
+
yield* _(ensureGeminiOrchLayout(cwd));
|
|
6777
|
+
const { accountLabel, accountPath } = resolveGeminiAccountPath(path, resolvePathFromCwd(path, cwd, command.geminiAuthPath), command.label);
|
|
6778
|
+
yield* _(fs.makeDirectory(accountPath, { recursive: true }));
|
|
6779
|
+
if (options.buildImage === true) yield* _(ensureDockerImage(fs, path, cwd, {
|
|
6780
|
+
imageName: geminiImageName,
|
|
6781
|
+
imageDir: geminiImageDir,
|
|
6782
|
+
dockerfile: renderGeminiDockerfile(),
|
|
6783
|
+
buildLabel: "gemini auth"
|
|
6784
|
+
}));
|
|
6785
|
+
return yield* _(run({
|
|
6786
|
+
accountLabel,
|
|
6787
|
+
accountPath,
|
|
6788
|
+
cwd,
|
|
6789
|
+
fs
|
|
6790
|
+
}));
|
|
6791
|
+
}));
|
|
6792
|
+
var readApiKey = (fs, accountPath) => Effect.gen(function* (_) {
|
|
6793
|
+
const apiKeyFilePath = geminiApiKeyPath(accountPath);
|
|
6794
|
+
if (yield* _(isRegularFile$1(fs, apiKeyFilePath))) {
|
|
6795
|
+
const trimmed = (yield* _(fs.readFileString(apiKeyFilePath), Effect.orElseSucceed(() => ""))).trim();
|
|
6796
|
+
if (trimmed.length > 0) return trimmed;
|
|
6797
|
+
}
|
|
6798
|
+
const envFilePath = geminiEnvFilePath(accountPath);
|
|
6799
|
+
if (yield* _(isRegularFile$1(fs, envFilePath))) {
|
|
6800
|
+
const lines = (yield* _(fs.readFileString(envFilePath), Effect.orElseSucceed(() => ""))).split("\n");
|
|
6801
|
+
for (const line of lines) {
|
|
6802
|
+
const trimmed = line.trim();
|
|
6803
|
+
if (trimmed.startsWith("GEMINI_API_KEY=")) {
|
|
6804
|
+
const value = trimmed.slice(15).replaceAll(/^['"]|['"]$/g, "").trim();
|
|
6805
|
+
if (value.length > 0) return value;
|
|
6806
|
+
}
|
|
6807
|
+
}
|
|
6808
|
+
}
|
|
6809
|
+
return null;
|
|
6810
|
+
});
|
|
6811
|
+
var hasOauthCredentials$1 = (fs, accountPath) => Effect.gen(function* (_) {
|
|
6812
|
+
const credentialsDir = geminiCredentialsPath(accountPath);
|
|
6813
|
+
if (!(yield* _(fs.exists(credentialsDir)))) return false;
|
|
6814
|
+
const possibleFiles = [
|
|
6815
|
+
`${credentialsDir}/oauth-tokens.json`,
|
|
6816
|
+
`${credentialsDir}/credentials.json`,
|
|
6817
|
+
`${credentialsDir}/application_default_credentials.json`
|
|
6818
|
+
];
|
|
6819
|
+
for (const filePath of possibleFiles) if (yield* _(isRegularFile$1(fs, filePath))) return true;
|
|
6820
|
+
return false;
|
|
6821
|
+
});
|
|
6822
|
+
var resolveGeminiAuthMethod = (fs, accountPath) => Effect.gen(function* (_) {
|
|
6823
|
+
if ((yield* _(readApiKey(fs, accountPath))) !== null) return "api-key";
|
|
6824
|
+
return (yield* _(hasOauthCredentials$1(fs, accountPath))) ? "oauth" : "none";
|
|
6825
|
+
});
|
|
6826
|
+
var authGeminiLogin = (command, apiKey) => {
|
|
6827
|
+
const accountLabel = normalizeAccountLabel(command.label, "default");
|
|
6828
|
+
return withGeminiAuth(command, ({ accountPath, fs }) => Effect.gen(function* (_) {
|
|
6829
|
+
const apiKeyFilePath = geminiApiKeyPath(accountPath);
|
|
6830
|
+
yield* _(fs.writeFileString(apiKeyFilePath, `${apiKey.trim()}\n`));
|
|
6831
|
+
yield* _(fs.chmod(apiKeyFilePath, 384), Effect.orElseSucceed(() => void 0));
|
|
6832
|
+
})).pipe(Effect.zipRight(autoSyncState(`chore(state): auth gemini ${accountLabel}`)));
|
|
6833
|
+
};
|
|
6834
|
+
var authGeminiLoginCli = (_command) => Effect.gen(function* (_) {
|
|
6835
|
+
yield* _(Effect.log("Gemini CLI supports two authentication methods:"));
|
|
6836
|
+
yield* _(Effect.log(""));
|
|
6837
|
+
yield* _(Effect.log("1. API Key (recommended for simplicity):"));
|
|
6838
|
+
yield* _(Effect.log(" - Go to https://ai.google.dev/aistudio"));
|
|
6839
|
+
yield* _(Effect.log(" - Create or retrieve your API key"));
|
|
6840
|
+
yield* _(Effect.log(" - Use: docker-git menu -> Auth profiles -> Gemini CLI: set API key"));
|
|
6841
|
+
yield* _(Effect.log(""));
|
|
6842
|
+
yield* _(Effect.log("2. OAuth (Sign in with Google):"));
|
|
6843
|
+
yield* _(Effect.log(" - Use: docker-git menu -> Auth profiles -> Gemini CLI: login via OAuth"));
|
|
6844
|
+
yield* _(Effect.log(" - Follow the prompts to authenticate with your Google account"));
|
|
6845
|
+
});
|
|
6846
|
+
var prepareGeminiCredentialsDir = (cwd, accountPath, fs) => Effect.gen(function* (_) {
|
|
6847
|
+
const credentialsDir = geminiCredentialsPath(accountPath);
|
|
6848
|
+
const removeFallback = pipe(runCommandExitCode({
|
|
6849
|
+
cwd,
|
|
6850
|
+
command: "docker",
|
|
6851
|
+
args: [
|
|
6852
|
+
"run",
|
|
6853
|
+
"--rm",
|
|
6854
|
+
"-v",
|
|
6855
|
+
`${accountPath}:/target`,
|
|
6856
|
+
"alpine",
|
|
6857
|
+
"rm",
|
|
6858
|
+
"-rf",
|
|
6859
|
+
"/target/.gemini"
|
|
6860
|
+
]
|
|
6861
|
+
}), Effect.asVoid, Effect.orElse(() => Effect.void));
|
|
6862
|
+
yield* _(fs.remove(credentialsDir, {
|
|
6863
|
+
recursive: true,
|
|
6864
|
+
force: true
|
|
6865
|
+
}).pipe(Effect.orElse(() => removeFallback)));
|
|
6866
|
+
yield* _(fs.makeDirectory(credentialsDir, { recursive: true }));
|
|
6867
|
+
return credentialsDir;
|
|
6868
|
+
});
|
|
6869
|
+
var writeInitialSettings = (credentialsDir, fs) => Effect.gen(function* (_) {
|
|
6870
|
+
const settingsPath = `${credentialsDir}/settings.json`;
|
|
6871
|
+
yield* _(fs.writeFileString(settingsPath, JSON.stringify({ security: { folderTrust: { enabled: false } } })));
|
|
6872
|
+
const trustedFoldersPath = `${credentialsDir}/trustedFolders.json`;
|
|
6873
|
+
yield* _(fs.writeFileString(trustedFoldersPath, JSON.stringify({
|
|
6874
|
+
"/": "TRUST_FOLDER",
|
|
6875
|
+
[geminiContainerHomeDir]: "TRUST_FOLDER"
|
|
6876
|
+
})));
|
|
6877
|
+
return settingsPath;
|
|
6878
|
+
});
|
|
6879
|
+
var authGeminiLoginOauth = (command) => {
|
|
6880
|
+
const accountLabel = normalizeAccountLabel(command.label, "default");
|
|
6881
|
+
return withGeminiAuth(command, ({ accountPath, cwd, fs }) => Effect.gen(function* (_) {
|
|
6882
|
+
const settingsPath = yield* _(writeInitialSettings(yield* _(prepareGeminiCredentialsDir(cwd, accountPath, fs)), fs));
|
|
6883
|
+
yield* _(runGeminiOauthLoginWithPrompt(cwd, accountPath, {
|
|
6884
|
+
image: geminiImageName,
|
|
6885
|
+
containerPath: geminiContainerHomeDir
|
|
6886
|
+
}));
|
|
6887
|
+
yield* _(fs.writeFileString(settingsPath, JSON.stringify({ security: {
|
|
6888
|
+
folderTrust: { enabled: false },
|
|
6889
|
+
auth: { selectedType: "oauth-personal" },
|
|
6890
|
+
approvalPolicy: "never"
|
|
6891
|
+
} }, null, 2) + "\n"));
|
|
6892
|
+
}), { buildImage: true }).pipe(Effect.zipRight(autoSyncState(`chore(state): auth gemini oauth ${accountLabel}`)));
|
|
6893
|
+
};
|
|
6894
|
+
var authGeminiStatus = (command) => withGeminiAuth(command, ({ accountLabel, accountPath, fs }) => Effect.gen(function* (_) {
|
|
6895
|
+
const authMethod = yield* _(resolveGeminiAuthMethod(fs, accountPath));
|
|
6896
|
+
if (authMethod === "none") {
|
|
6897
|
+
yield* _(Effect.log(`Gemini not connected (${accountLabel}).`));
|
|
6898
|
+
return;
|
|
6899
|
+
}
|
|
6900
|
+
yield* _(Effect.log(`Gemini connected (${accountLabel}, ${authMethod}).`));
|
|
6901
|
+
}));
|
|
6902
|
+
var authGeminiLogout = (command) => Effect.gen(function* (_) {
|
|
6903
|
+
const accountLabel = normalizeAccountLabel(command.label, "default");
|
|
6904
|
+
yield* _(withGeminiAuth(command, ({ accountPath, fs }) => Effect.gen(function* (_) {
|
|
6905
|
+
yield* _(fs.remove(geminiApiKeyPath(accountPath), { force: true }));
|
|
6906
|
+
yield* _(fs.remove(geminiEnvFilePath(accountPath), { force: true }));
|
|
6907
|
+
yield* _(fs.remove(geminiCredentialsPath(accountPath), {
|
|
6908
|
+
recursive: true,
|
|
6909
|
+
force: true
|
|
6910
|
+
}));
|
|
6911
|
+
})));
|
|
6912
|
+
yield* _(autoSyncState(`chore(state): auth gemini logout ${accountLabel}`));
|
|
6913
|
+
}).pipe(Effect.asVoid);
|
|
6914
|
+
//#endregion
|
|
6352
6915
|
//#region ../lib/src/usecases/state-repo-github.ts
|
|
6353
6916
|
var dotDockerGitRepoName = ".docker-git";
|
|
6354
6917
|
var defaultStateRef = "main";
|
|
@@ -7646,10 +8209,12 @@ var normalizeLabel$1 = (value) => {
|
|
|
7646
8209
|
var defaultEnvGlobalPath = ".docker-git/.orch/env/global.env";
|
|
7647
8210
|
var defaultCodexAuthPath = ".docker-git/.orch/auth/codex";
|
|
7648
8211
|
var defaultClaudeAuthPath = ".docker-git/.orch/auth/claude";
|
|
8212
|
+
var defaultGeminiAuthPath = ".docker-git/.orch/auth/gemini";
|
|
7649
8213
|
var resolveAuthOptions = (raw) => ({
|
|
7650
8214
|
envGlobalPath: raw.envGlobalPath ?? defaultEnvGlobalPath,
|
|
7651
8215
|
codexAuthPath: raw.codexAuthPath ?? defaultCodexAuthPath,
|
|
7652
8216
|
claudeAuthPath: defaultClaudeAuthPath,
|
|
8217
|
+
geminiAuthPath: defaultGeminiAuthPath,
|
|
7653
8218
|
label: normalizeLabel$1(raw.label),
|
|
7654
8219
|
token: normalizeLabel$1(raw.token),
|
|
7655
8220
|
scopes: normalizeLabel$1(raw.scopes),
|
|
@@ -7695,7 +8260,21 @@ var buildClaudeCommand = (action, options) => Match.value(action).pipe(Match.whe
|
|
|
7695
8260
|
label: options.label,
|
|
7696
8261
|
claudeAuthPath: options.claudeAuthPath
|
|
7697
8262
|
})), Match.orElse(() => Either.left(invalidArgument("auth action", `unknown action '${action}'`))));
|
|
7698
|
-
var
|
|
8263
|
+
var buildGeminiCommand = (action, options) => Match.value(action).pipe(Match.when("login", () => Either.right({
|
|
8264
|
+
_tag: "AuthGeminiLogin",
|
|
8265
|
+
label: options.label,
|
|
8266
|
+
geminiAuthPath: options.geminiAuthPath,
|
|
8267
|
+
isWeb: options.authWeb
|
|
8268
|
+
})), Match.when("status", () => Either.right({
|
|
8269
|
+
_tag: "AuthGeminiStatus",
|
|
8270
|
+
label: options.label,
|
|
8271
|
+
geminiAuthPath: options.geminiAuthPath
|
|
8272
|
+
})), Match.when("logout", () => Either.right({
|
|
8273
|
+
_tag: "AuthGeminiLogout",
|
|
8274
|
+
label: options.label,
|
|
8275
|
+
geminiAuthPath: options.geminiAuthPath
|
|
8276
|
+
})), Match.orElse(() => Either.left(invalidArgument("auth action", `unknown action '${action}'`))));
|
|
8277
|
+
var buildAuthCommand = (provider, action, options) => Match.value(provider).pipe(Match.when("github", () => buildGithubCommand(action, options)), Match.when("gh", () => buildGithubCommand(action, options)), Match.when("codex", () => buildCodexCommand(action, options)), Match.when("claude", () => buildClaudeCommand(action, options)), Match.when("cc", () => buildClaudeCommand(action, options)), Match.when("gemini", () => buildGeminiCommand(action, options)), Match.orElse(() => Either.left(invalidArgument("auth provider", `unknown provider '${provider}'`))));
|
|
7699
8278
|
var parseAuth = (args) => {
|
|
7700
8279
|
if (args.length < 2) return Either.left(missingArgument(args.length === 0 ? "auth provider" : "auth action"));
|
|
7701
8280
|
const provider = args[0] ?? "";
|
|
@@ -7802,13 +8381,15 @@ var buildDefaultPathConfig = (normalizedSecretsRoot) => normalizedSecretsRoot ==
|
|
|
7802
8381
|
authorizedKeysPath: defaultTemplateConfig.authorizedKeysPath,
|
|
7803
8382
|
envGlobalPath: defaultTemplateConfig.envGlobalPath,
|
|
7804
8383
|
envProjectPath: defaultTemplateConfig.envProjectPath,
|
|
7805
|
-
codexAuthPath: defaultTemplateConfig.codexAuthPath
|
|
8384
|
+
codexAuthPath: defaultTemplateConfig.codexAuthPath,
|
|
8385
|
+
geminiAuthPath: defaultTemplateConfig.geminiAuthPath
|
|
7806
8386
|
} : {
|
|
7807
8387
|
dockerGitPath: defaultTemplateConfig.dockerGitPath,
|
|
7808
8388
|
authorizedKeysPath: defaultTemplateConfig.authorizedKeysPath,
|
|
7809
8389
|
envGlobalPath: `${normalizedSecretsRoot}/global.env`,
|
|
7810
8390
|
envProjectPath: defaultTemplateConfig.envProjectPath,
|
|
7811
|
-
codexAuthPath: `${normalizedSecretsRoot}/codex
|
|
8391
|
+
codexAuthPath: `${normalizedSecretsRoot}/codex`,
|
|
8392
|
+
geminiAuthPath: `${normalizedSecretsRoot}/gemini`
|
|
7812
8393
|
};
|
|
7813
8394
|
var resolvePaths = (raw, repoPath) => Either.gen(function* (_) {
|
|
7814
8395
|
const defaults = buildDefaultPathConfig(resolveNormalizedSecretsRoot(raw.secretsRoot));
|
|
@@ -7825,6 +8406,8 @@ var resolvePaths = (raw, repoPath) => Either.gen(function* (_) {
|
|
|
7825
8406
|
codexAuthPath,
|
|
7826
8407
|
codexSharedAuthPath: codexAuthPath,
|
|
7827
8408
|
codexHome: yield* _(nonEmpty("--codex-home", raw.codexHome, defaultTemplateConfig.codexHome)),
|
|
8409
|
+
geminiAuthPath: defaults.geminiAuthPath,
|
|
8410
|
+
geminiHome: defaultTemplateConfig.geminiHome,
|
|
7828
8411
|
outDir: yield* _(nonEmpty("--out-dir", raw.outDir, `.docker-git/${repoPath}`))
|
|
7829
8412
|
};
|
|
7830
8413
|
});
|
|
@@ -7854,6 +8437,8 @@ var buildTemplateConfig = ({ agentAuto, agentMode, claudeAuthLabel, codexAuthLab
|
|
|
7854
8437
|
codexAuthPath: paths.codexAuthPath,
|
|
7855
8438
|
codexSharedAuthPath: paths.codexSharedAuthPath,
|
|
7856
8439
|
codexHome: paths.codexHome,
|
|
8440
|
+
geminiAuthPath: paths.geminiAuthPath,
|
|
8441
|
+
geminiHome: paths.geminiHome,
|
|
7857
8442
|
cpuLimit,
|
|
7858
8443
|
ramLimit,
|
|
7859
8444
|
dockerNetworkMode,
|
|
@@ -8935,6 +9520,12 @@ var countAuthAccountDirectories = (fs, path, root) => Effect.gen(function* (_) {
|
|
|
8935
9520
|
return count;
|
|
8936
9521
|
});
|
|
8937
9522
|
//#endregion
|
|
9523
|
+
//#region src/docker-git/menu-auth-snapshot-builder.ts
|
|
9524
|
+
var countAuthAccountEntries = (fs, path, claudeAuthPath, geminiAuthPath) => pipe(Effect.all({
|
|
9525
|
+
claudeAuthEntries: countAuthAccountDirectories(fs, path, claudeAuthPath),
|
|
9526
|
+
geminiAuthEntries: countAuthAccountDirectories(fs, path, geminiAuthPath)
|
|
9527
|
+
}));
|
|
9528
|
+
//#endregion
|
|
8938
9529
|
//#region src/docker-git/menu-labeled-env.ts
|
|
8939
9530
|
var normalizeLabel = (value) => {
|
|
8940
9531
|
const trimmed = value.trim();
|
|
@@ -8983,6 +9574,18 @@ var authMenuItems = [
|
|
|
8983
9574
|
action: "ClaudeLogout",
|
|
8984
9575
|
label: "Claude Code: logout (clear cache)"
|
|
8985
9576
|
},
|
|
9577
|
+
{
|
|
9578
|
+
action: "GeminiOauth",
|
|
9579
|
+
label: "Gemini CLI: login via OAuth (Google account)"
|
|
9580
|
+
},
|
|
9581
|
+
{
|
|
9582
|
+
action: "GeminiApiKey",
|
|
9583
|
+
label: "Gemini CLI: set API key"
|
|
9584
|
+
},
|
|
9585
|
+
{
|
|
9586
|
+
action: "GeminiLogout",
|
|
9587
|
+
label: "Gemini CLI: logout (clear credentials)"
|
|
9588
|
+
},
|
|
8986
9589
|
{
|
|
8987
9590
|
action: "Refresh",
|
|
8988
9591
|
label: "Refresh snapshot"
|
|
@@ -9042,34 +9645,62 @@ var flowSteps$1 = {
|
|
|
9042
9645
|
label: "Label to logout (empty = default)",
|
|
9043
9646
|
required: false,
|
|
9044
9647
|
secret: false
|
|
9648
|
+
}],
|
|
9649
|
+
GeminiOauth: [{
|
|
9650
|
+
key: "label",
|
|
9651
|
+
label: "Label (empty = default)",
|
|
9652
|
+
required: false,
|
|
9653
|
+
secret: false
|
|
9654
|
+
}],
|
|
9655
|
+
GeminiApiKey: [{
|
|
9656
|
+
key: "label",
|
|
9657
|
+
label: "Label (empty = default)",
|
|
9658
|
+
required: false,
|
|
9659
|
+
secret: false
|
|
9660
|
+
}, {
|
|
9661
|
+
key: "apiKey",
|
|
9662
|
+
label: "Gemini API key (from ai.google.dev)",
|
|
9663
|
+
required: true,
|
|
9664
|
+
secret: true
|
|
9665
|
+
}],
|
|
9666
|
+
GeminiLogout: [{
|
|
9667
|
+
key: "label",
|
|
9668
|
+
label: "Label to logout (empty = default)",
|
|
9669
|
+
required: false,
|
|
9670
|
+
secret: false
|
|
9045
9671
|
}]
|
|
9046
9672
|
};
|
|
9047
|
-
var flowTitle = (flow) => Match.value(flow).pipe(Match.when("GithubOauth", () => "GitHub OAuth"), Match.when("GithubRemove", () => "GitHub remove"), Match.when("GitSet", () => "Git credentials"), Match.when("GitRemove", () => "Git remove"), Match.when("ClaudeOauth", () => "Claude Code OAuth"), Match.when("ClaudeLogout", () => "Claude Code logout"), Match.exhaustive);
|
|
9048
|
-
var successMessage$1 = (flow, label) => Match.value(flow).pipe(Match.when("GithubOauth", () => `Saved GitHub token (${label}).`), Match.when("GithubRemove", () => `Removed GitHub token (${label}).`), Match.when("GitSet", () => `Saved Git credentials (${label}).`), Match.when("GitRemove", () => `Removed Git credentials (${label}).`), Match.when("ClaudeOauth", () => `Saved Claude Code login (${label}).`), Match.when("ClaudeLogout", () => `Logged out Claude Code (${label}).`), Match.exhaustive);
|
|
9673
|
+
var flowTitle = (flow) => Match.value(flow).pipe(Match.when("GithubOauth", () => "GitHub OAuth"), Match.when("GithubRemove", () => "GitHub remove"), Match.when("GitSet", () => "Git credentials"), Match.when("GitRemove", () => "Git remove"), Match.when("ClaudeOauth", () => "Claude Code OAuth"), Match.when("ClaudeLogout", () => "Claude Code logout"), Match.when("GeminiOauth", () => "Gemini CLI OAuth"), Match.when("GeminiApiKey", () => "Gemini CLI API key"), Match.when("GeminiLogout", () => "Gemini CLI logout"), Match.exhaustive);
|
|
9674
|
+
var successMessage$1 = (flow, label) => Match.value(flow).pipe(Match.when("GithubOauth", () => `Saved GitHub token (${label}).`), Match.when("GithubRemove", () => `Removed GitHub token (${label}).`), Match.when("GitSet", () => `Saved Git credentials (${label}).`), Match.when("GitRemove", () => `Removed Git credentials (${label}).`), Match.when("ClaudeOauth", () => `Saved Claude Code login (${label}).`), Match.when("ClaudeLogout", () => `Logged out Claude Code (${label}).`), Match.when("GeminiOauth", () => `Saved Gemini CLI OAuth login (${label}).`), Match.when("GeminiApiKey", () => `Saved Gemini API key (${label}).`), Match.when("GeminiLogout", () => `Logged out Gemini CLI (${label}).`), Match.exhaustive);
|
|
9049
9675
|
var buildGlobalEnvPath$1 = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/env/global.env`;
|
|
9050
9676
|
var buildClaudeAuthPath$1 = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/auth/claude`;
|
|
9677
|
+
var buildGeminiAuthPath$1 = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/auth/gemini`;
|
|
9051
9678
|
var loadAuthEnvText = (cwd) => Effect.gen(function* (_) {
|
|
9052
9679
|
const fs = yield* _(FileSystem.FileSystem);
|
|
9053
9680
|
const path = yield* _(Path.Path);
|
|
9054
9681
|
const globalEnvPath = buildGlobalEnvPath$1(cwd);
|
|
9055
9682
|
const claudeAuthPath = buildClaudeAuthPath$1(cwd);
|
|
9683
|
+
const geminiAuthPath = buildGeminiAuthPath$1(cwd);
|
|
9056
9684
|
yield* _(ensureEnvFile$1(fs, path, globalEnvPath));
|
|
9057
9685
|
return {
|
|
9058
9686
|
fs,
|
|
9059
9687
|
path,
|
|
9060
9688
|
globalEnvPath,
|
|
9061
9689
|
claudeAuthPath,
|
|
9690
|
+
geminiAuthPath,
|
|
9062
9691
|
envText: yield* _(readEnvText(fs, globalEnvPath))
|
|
9063
9692
|
};
|
|
9064
9693
|
});
|
|
9065
|
-
var readAuthSnapshot = (cwd) => pipe(loadAuthEnvText(cwd), Effect.flatMap(({ claudeAuthPath, envText, fs, globalEnvPath, path }) =>
|
|
9694
|
+
var readAuthSnapshot = (cwd) => pipe(loadAuthEnvText(cwd), Effect.flatMap(({ claudeAuthPath, envText, fs, geminiAuthPath, globalEnvPath, path }) => countAuthAccountEntries(fs, path, claudeAuthPath, geminiAuthPath).pipe(Effect.map(({ claudeAuthEntries, geminiAuthEntries }) => ({
|
|
9066
9695
|
globalEnvPath,
|
|
9067
9696
|
claudeAuthPath,
|
|
9697
|
+
geminiAuthPath,
|
|
9068
9698
|
totalEntries: parseEnvEntries(envText).filter((entry) => entry.value.trim().length > 0).length,
|
|
9069
9699
|
githubTokenEntries: countKeyEntries(envText, "GITHUB_TOKEN"),
|
|
9070
9700
|
gitTokenEntries: countKeyEntries(envText, "GIT_AUTH_TOKEN"),
|
|
9071
9701
|
gitUserEntries: countKeyEntries(envText, "GIT_AUTH_USER"),
|
|
9072
|
-
claudeAuthEntries
|
|
9702
|
+
claudeAuthEntries,
|
|
9703
|
+
geminiAuthEntries
|
|
9073
9704
|
})))));
|
|
9074
9705
|
var writeAuthFlow = (cwd, flow, values) => pipe(loadAuthEnvText(cwd), Effect.flatMap(({ envText, fs, globalEnvPath }) => {
|
|
9075
9706
|
const label = values["label"] ?? "";
|
|
@@ -9098,6 +9729,69 @@ var authMenuActionByIndex = (index) => {
|
|
|
9098
9729
|
};
|
|
9099
9730
|
var authMenuSize = () => authMenuItems.length;
|
|
9100
9731
|
//#endregion
|
|
9732
|
+
//#region src/docker-git/menu-auth-effects.ts
|
|
9733
|
+
var resolveLabelOption = (values) => {
|
|
9734
|
+
const labelValue = (values["label"] ?? "").trim();
|
|
9735
|
+
return labelValue.length > 0 ? labelValue : null;
|
|
9736
|
+
};
|
|
9737
|
+
var resolveGithubOauthEffect = (labelOption, globalEnvPath) => authGithubLogin({
|
|
9738
|
+
_tag: "AuthGithubLogin",
|
|
9739
|
+
label: labelOption,
|
|
9740
|
+
token: null,
|
|
9741
|
+
scopes: null,
|
|
9742
|
+
envGlobalPath: globalEnvPath
|
|
9743
|
+
});
|
|
9744
|
+
var resolveClaudeOauthEffect = (labelOption) => authClaudeLogin({
|
|
9745
|
+
_tag: "AuthClaudeLogin",
|
|
9746
|
+
label: labelOption,
|
|
9747
|
+
claudeAuthPath: claudeAuthRoot
|
|
9748
|
+
});
|
|
9749
|
+
var resolveClaudeLogoutEffect = (labelOption) => authClaudeLogout({
|
|
9750
|
+
_tag: "AuthClaudeLogout",
|
|
9751
|
+
label: labelOption,
|
|
9752
|
+
claudeAuthPath: claudeAuthRoot
|
|
9753
|
+
});
|
|
9754
|
+
var resolveGeminiOauthEffect = (labelOption) => authGeminiLoginOauth({
|
|
9755
|
+
_tag: "AuthGeminiLogin",
|
|
9756
|
+
label: labelOption,
|
|
9757
|
+
geminiAuthPath: geminiAuthRoot,
|
|
9758
|
+
isWeb: false
|
|
9759
|
+
});
|
|
9760
|
+
var resolveGeminiApiKeyEffect = (labelOption, apiKey) => authGeminiLogin({
|
|
9761
|
+
_tag: "AuthGeminiLogin",
|
|
9762
|
+
label: labelOption,
|
|
9763
|
+
geminiAuthPath: geminiAuthRoot,
|
|
9764
|
+
isWeb: false
|
|
9765
|
+
}, apiKey);
|
|
9766
|
+
var resolveGeminiLogoutEffect = (labelOption) => authGeminiLogout({
|
|
9767
|
+
_tag: "AuthGeminiLogout",
|
|
9768
|
+
label: labelOption,
|
|
9769
|
+
geminiAuthPath: geminiAuthRoot
|
|
9770
|
+
});
|
|
9771
|
+
var resolveAuthPromptEffect = (view, cwd, values) => {
|
|
9772
|
+
const labelOption = resolveLabelOption(values);
|
|
9773
|
+
return Match.value(view.flow).pipe(Match.when("GithubOauth", () => resolveGithubOauthEffect(labelOption, view.snapshot.globalEnvPath)), Match.when("ClaudeOauth", () => resolveClaudeOauthEffect(labelOption)), Match.when("ClaudeLogout", () => resolveClaudeLogoutEffect(labelOption)), Match.when("GeminiOauth", () => resolveGeminiOauthEffect(labelOption)), Match.when("GeminiApiKey", () => resolveGeminiApiKeyEffect(labelOption, (values["apiKey"] ?? "").trim())), Match.when("GeminiLogout", () => resolveGeminiLogoutEffect(labelOption)), Match.when("GithubRemove", (flow) => writeAuthFlow(cwd, flow, values)), Match.when("GitSet", (flow) => writeAuthFlow(cwd, flow, values)), Match.when("GitRemove", (flow) => writeAuthFlow(cwd, flow, values)), Match.exhaustive);
|
|
9774
|
+
};
|
|
9775
|
+
var startAuthMenuWithSnapshot = (snapshot, context) => {
|
|
9776
|
+
context.setView({
|
|
9777
|
+
_tag: "AuthMenu",
|
|
9778
|
+
selected: 0,
|
|
9779
|
+
snapshot
|
|
9780
|
+
});
|
|
9781
|
+
context.setMessage(null);
|
|
9782
|
+
};
|
|
9783
|
+
var runAuthPromptEffect = (effect, view, label, context, options) => {
|
|
9784
|
+
const withOptionalSuspension = options.suspendTui ? withSuspendedTui(effect, {
|
|
9785
|
+
onError: pauseOnError(renderError),
|
|
9786
|
+
onResume: resumeSshWithSkipInputs(context)
|
|
9787
|
+
}) : effect;
|
|
9788
|
+
context.setSshActive(options.suspendTui);
|
|
9789
|
+
context.runner.runEffect(pipe(withOptionalSuspension, Effect.zipRight(readAuthSnapshot(context.cwd)), Effect.tap((snapshot) => Effect.sync(() => {
|
|
9790
|
+
startAuthMenuWithSnapshot(snapshot, context);
|
|
9791
|
+
context.setMessage(successMessage$1(view.flow, label));
|
|
9792
|
+
})), Effect.asVoid));
|
|
9793
|
+
};
|
|
9794
|
+
//#endregion
|
|
9101
9795
|
//#region src/docker-git/menu-input-utils.ts
|
|
9102
9796
|
var parseMenuIndex = (input) => {
|
|
9103
9797
|
const trimmed = input.trim();
|
|
@@ -9154,14 +9848,6 @@ var defaultLabel = (value) => {
|
|
|
9154
9848
|
const trimmed = value.trim();
|
|
9155
9849
|
return trimmed.length > 0 ? trimmed : "default";
|
|
9156
9850
|
};
|
|
9157
|
-
var startAuthMenuWithSnapshot = (snapshot, context) => {
|
|
9158
|
-
context.setView({
|
|
9159
|
-
_tag: "AuthMenu",
|
|
9160
|
-
selected: 0,
|
|
9161
|
-
snapshot
|
|
9162
|
-
});
|
|
9163
|
-
context.setMessage(null);
|
|
9164
|
-
};
|
|
9165
9851
|
var startAuthPrompt = (snapshot, flow, context) => {
|
|
9166
9852
|
context.setView({
|
|
9167
9853
|
_tag: "AuthPrompt",
|
|
@@ -9173,39 +9859,6 @@ var startAuthPrompt = (snapshot, flow, context) => {
|
|
|
9173
9859
|
});
|
|
9174
9860
|
context.setMessage(null);
|
|
9175
9861
|
};
|
|
9176
|
-
var resolveLabelOption = (values) => {
|
|
9177
|
-
const labelValue = (values["label"] ?? "").trim();
|
|
9178
|
-
return labelValue.length > 0 ? labelValue : null;
|
|
9179
|
-
};
|
|
9180
|
-
var resolveAuthPromptEffect = (view, cwd, values) => {
|
|
9181
|
-
const labelOption = resolveLabelOption(values);
|
|
9182
|
-
return Match.value(view.flow).pipe(Match.when("GithubOauth", () => authGithubLogin({
|
|
9183
|
-
_tag: "AuthGithubLogin",
|
|
9184
|
-
label: labelOption,
|
|
9185
|
-
token: null,
|
|
9186
|
-
scopes: null,
|
|
9187
|
-
envGlobalPath: view.snapshot.globalEnvPath
|
|
9188
|
-
})), Match.when("ClaudeOauth", () => authClaudeLogin({
|
|
9189
|
-
_tag: "AuthClaudeLogin",
|
|
9190
|
-
label: labelOption,
|
|
9191
|
-
claudeAuthPath: claudeAuthRoot
|
|
9192
|
-
})), Match.when("ClaudeLogout", () => authClaudeLogout({
|
|
9193
|
-
_tag: "AuthClaudeLogout",
|
|
9194
|
-
label: labelOption,
|
|
9195
|
-
claudeAuthPath: claudeAuthRoot
|
|
9196
|
-
})), Match.when("GithubRemove", (flow) => writeAuthFlow(cwd, flow, values)), Match.when("GitSet", (flow) => writeAuthFlow(cwd, flow, values)), Match.when("GitRemove", (flow) => writeAuthFlow(cwd, flow, values)), Match.exhaustive);
|
|
9197
|
-
};
|
|
9198
|
-
var runAuthPromptEffect = (effect, view, label, context, options) => {
|
|
9199
|
-
const withOptionalSuspension = options.suspendTui ? withSuspendedTui(effect, {
|
|
9200
|
-
onError: pauseOnError(renderError),
|
|
9201
|
-
onResume: resumeSshWithSkipInputs(context)
|
|
9202
|
-
}) : effect;
|
|
9203
|
-
context.setSshActive(options.suspendTui);
|
|
9204
|
-
context.runner.runEffect(pipe(withOptionalSuspension, Effect.zipRight(readAuthSnapshot(context.state.cwd)), Effect.tap((snapshot) => Effect.sync(() => {
|
|
9205
|
-
startAuthMenuWithSnapshot(snapshot, context);
|
|
9206
|
-
context.setMessage(successMessage$1(view.flow, label));
|
|
9207
|
-
})), Effect.asVoid));
|
|
9208
|
-
};
|
|
9209
9862
|
var loadAuthMenuView = (cwd, context) => pipe(readAuthSnapshot(cwd), Effect.tap((snapshot) => Effect.sync(() => {
|
|
9210
9863
|
startAuthMenuWithSnapshot(snapshot, context);
|
|
9211
9864
|
})), Effect.asVoid);
|
|
@@ -9225,7 +9878,10 @@ var submitAuthPrompt = (view, context) => {
|
|
|
9225
9878
|
startAuthMenuWithSnapshot(view.snapshot, context);
|
|
9226
9879
|
}, (nextValues) => {
|
|
9227
9880
|
const label = defaultLabel(nextValues["label"] ?? "");
|
|
9228
|
-
runAuthPromptEffect(resolveAuthPromptEffect(view, context.state.cwd, nextValues), view, label,
|
|
9881
|
+
runAuthPromptEffect(resolveAuthPromptEffect(view, context.state.cwd, nextValues), view, label, {
|
|
9882
|
+
...context,
|
|
9883
|
+
cwd: context.state.cwd
|
|
9884
|
+
}, { suspendTui: view.flow === "GithubOauth" || view.flow === "ClaudeOauth" || view.flow === "ClaudeLogout" || view.flow === "GeminiOauth" });
|
|
9229
9885
|
});
|
|
9230
9886
|
};
|
|
9231
9887
|
var setAuthMenuSelection = (view, selected, context) => {
|
|
@@ -9267,6 +9923,15 @@ var handleAuthMenuInput = (input, key, view, context) => {
|
|
|
9267
9923
|
}
|
|
9268
9924
|
handleAuthMenuNumberInput(input, view, context);
|
|
9269
9925
|
};
|
|
9926
|
+
var setAuthPromptBuffer = (args) => {
|
|
9927
|
+
const { context, input, key, view } = args;
|
|
9928
|
+
const nextBuffer = nextBufferValue(input, key, view.buffer);
|
|
9929
|
+
if (nextBuffer === null) return;
|
|
9930
|
+
context.setView({
|
|
9931
|
+
...view,
|
|
9932
|
+
buffer: nextBuffer
|
|
9933
|
+
});
|
|
9934
|
+
};
|
|
9270
9935
|
var handleAuthPromptInput = (input, key, view, context) => {
|
|
9271
9936
|
if (key.escape) {
|
|
9272
9937
|
startAuthMenuWithSnapshot(view.snapshot, context);
|
|
@@ -9283,15 +9948,6 @@ var handleAuthPromptInput = (input, key, view, context) => {
|
|
|
9283
9948
|
context
|
|
9284
9949
|
});
|
|
9285
9950
|
};
|
|
9286
|
-
var setAuthPromptBuffer = (args) => {
|
|
9287
|
-
const { context, input, key, view } = args;
|
|
9288
|
-
const nextBuffer = nextBufferValue(input, key, view.buffer);
|
|
9289
|
-
if (nextBuffer === null) return;
|
|
9290
|
-
context.setView({
|
|
9291
|
-
...view,
|
|
9292
|
-
buffer: nextBuffer
|
|
9293
|
-
});
|
|
9294
|
-
};
|
|
9295
9951
|
var openAuthMenu = (context) => {
|
|
9296
9952
|
context.setMessage("Loading auth profiles...");
|
|
9297
9953
|
context.runner.runEffect(loadAuthMenuView(context.state.cwd, context));
|
|
@@ -9384,15 +10040,17 @@ var loadRuntimeByProject = (items) => pipe(runDockerPsNames(process.cwd()), Effe
|
|
|
9384
10040
|
}));
|
|
9385
10041
|
var runtimeForSelection = (view, selected) => view.runtimeByProject[selected.projectDir] ?? stoppedRuntime$1();
|
|
9386
10042
|
//#endregion
|
|
10043
|
+
//#region src/docker-git/menu-project-auth-helpers.ts
|
|
10044
|
+
var hasFileAtPath = (fs, filePath) => Effect.gen(function* (_) {
|
|
10045
|
+
if (!(yield* _(fs.exists(filePath)))) return false;
|
|
10046
|
+
return (yield* _(fs.stat(filePath))).type === "File";
|
|
10047
|
+
});
|
|
10048
|
+
//#endregion
|
|
9387
10049
|
//#region src/docker-git/menu-project-auth-claude.ts
|
|
9388
10050
|
var oauthTokenFileName = ".oauth-token";
|
|
9389
10051
|
var legacyConfigFileName = ".config.json";
|
|
9390
10052
|
var credentialsFileName = ".credentials.json";
|
|
9391
10053
|
var nestedCredentialsFileName = ".claude/.credentials.json";
|
|
9392
|
-
var hasFileAtPath = (fs, filePath) => Effect.gen(function* (_) {
|
|
9393
|
-
if (!(yield* _(fs.exists(filePath)))) return false;
|
|
9394
|
-
return (yield* _(fs.stat(filePath))).type === "File";
|
|
9395
|
-
});
|
|
9396
10054
|
var hasNonEmptyOauthToken = (fs, tokenPath) => Effect.gen(function* (_) {
|
|
9397
10055
|
if (!(yield* _(hasFileAtPath(fs, tokenPath)))) return false;
|
|
9398
10056
|
return (yield* _(fs.readFileString(tokenPath), Effect.orElseSucceed(() => ""))).trim().length > 0;
|
|
@@ -9419,6 +10077,107 @@ var hasClaudeAccountCredentials = (fs, accountPath) => hasFileAtPath(fs, `${acco
|
|
|
9419
10077
|
}));
|
|
9420
10078
|
}));
|
|
9421
10079
|
//#endregion
|
|
10080
|
+
//#region src/docker-git/menu-project-auth-gemini.ts
|
|
10081
|
+
var apiKeyFileName = ".api-key";
|
|
10082
|
+
var envFileName = ".env";
|
|
10083
|
+
var geminiCredentialsDir = ".gemini";
|
|
10084
|
+
var hasNonEmptyApiKey = (fs, apiKeyPath) => Effect.gen(function* (_) {
|
|
10085
|
+
if (!(yield* _(hasFileAtPath(fs, apiKeyPath)))) return false;
|
|
10086
|
+
return (yield* _(fs.readFileString(apiKeyPath), Effect.orElseSucceed(() => ""))).trim().length > 0;
|
|
10087
|
+
});
|
|
10088
|
+
var hasApiKeyInEnvFile = (fs, envFilePath) => Effect.gen(function* (_) {
|
|
10089
|
+
if (!(yield* _(hasFileAtPath(fs, envFilePath)))) return false;
|
|
10090
|
+
const lines = (yield* _(fs.readFileString(envFilePath), Effect.orElseSucceed(() => ""))).split("\n");
|
|
10091
|
+
for (const line of lines) {
|
|
10092
|
+
const trimmed = line.trim();
|
|
10093
|
+
if (trimmed.startsWith("GEMINI_API_KEY=")) {
|
|
10094
|
+
if (trimmed.slice(15).replaceAll(/^['"]|['"]$/g, "").trim().length > 0) return true;
|
|
10095
|
+
}
|
|
10096
|
+
}
|
|
10097
|
+
return false;
|
|
10098
|
+
});
|
|
10099
|
+
var geminiOauthCredentialFiles = [
|
|
10100
|
+
"oauth-tokens.json",
|
|
10101
|
+
"credentials.json",
|
|
10102
|
+
"application_default_credentials.json"
|
|
10103
|
+
];
|
|
10104
|
+
var checkAnyFileExists = (fs, basePath, fileNames) => {
|
|
10105
|
+
const [first, ...rest] = fileNames;
|
|
10106
|
+
if (first === void 0) return Effect.succeed(false);
|
|
10107
|
+
return hasFileAtPath(fs, `${basePath}/${first}`).pipe(Effect.flatMap((exists) => exists ? Effect.succeed(true) : checkAnyFileExists(fs, basePath, rest)));
|
|
10108
|
+
};
|
|
10109
|
+
var hasOauthCredentials = (fs, accountPath) => {
|
|
10110
|
+
const credentialsDir = `${accountPath}/${geminiCredentialsDir}`;
|
|
10111
|
+
return hasFileAtPath(fs, credentialsDir).pipe(Effect.flatMap((dirExists) => dirExists ? checkAnyFileExists(fs, credentialsDir, geminiOauthCredentialFiles) : Effect.succeed(false)));
|
|
10112
|
+
};
|
|
10113
|
+
var hasGeminiAccountCredentials = (fs, accountPath) => hasNonEmptyApiKey(fs, `${accountPath}/${apiKeyFileName}`).pipe(Effect.flatMap((hasApiKey) => {
|
|
10114
|
+
if (hasApiKey) return Effect.succeed(true);
|
|
10115
|
+
return hasApiKeyInEnvFile(fs, `${accountPath}/${envFileName}`).pipe(Effect.flatMap((hasEnvApiKey) => {
|
|
10116
|
+
if (hasEnvApiKey) return Effect.succeed(true);
|
|
10117
|
+
return hasOauthCredentials(fs, accountPath);
|
|
10118
|
+
}));
|
|
10119
|
+
}));
|
|
10120
|
+
//#endregion
|
|
10121
|
+
//#region src/docker-git/menu-project-auth-flows.ts
|
|
10122
|
+
var githubTokenBaseKey$1 = "GITHUB_TOKEN";
|
|
10123
|
+
var gitTokenBaseKey$1 = "GIT_AUTH_TOKEN";
|
|
10124
|
+
var gitUserBaseKey = "GIT_AUTH_USER";
|
|
10125
|
+
var projectGithubLabelKey$1 = "GITHUB_AUTH_LABEL";
|
|
10126
|
+
var projectGitLabelKey$1 = "GIT_AUTH_LABEL";
|
|
10127
|
+
var projectClaudeLabelKey$1 = "CLAUDE_AUTH_LABEL";
|
|
10128
|
+
var projectGeminiLabelKey$1 = "GEMINI_AUTH_LABEL";
|
|
10129
|
+
var defaultGitUser = "x-access-token";
|
|
10130
|
+
var missingSecret = (provider, label, envPath) => new AuthError({ message: `${provider} not connected: label '${label}' not found in ${envPath}` });
|
|
10131
|
+
var clearProjectGitLabels = (envText) => {
|
|
10132
|
+
return upsertEnvKey(upsertEnvKey(upsertEnvKey(envText, "GH_TOKEN", ""), projectGitLabelKey$1, ""), projectGithubLabelKey$1, "");
|
|
10133
|
+
};
|
|
10134
|
+
var updateProjectGithubConnect = (spec) => {
|
|
10135
|
+
const key = buildLabeledEnvKey(githubTokenBaseKey$1, spec.rawLabel);
|
|
10136
|
+
const token = findEnvValue(spec.globalEnvText, key);
|
|
10137
|
+
if (token === null) return Effect.fail(missingSecret("GitHub token", spec.canonicalLabel, spec.globalEnvPath));
|
|
10138
|
+
const withoutGitLabel = upsertEnvKey(upsertEnvKey(upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", token), "GH_TOKEN", token), projectGitLabelKey$1, "");
|
|
10139
|
+
return Effect.succeed(upsertEnvKey(withoutGitLabel, projectGithubLabelKey$1, spec.canonicalLabel));
|
|
10140
|
+
};
|
|
10141
|
+
var updateProjectGithubDisconnect = (spec) => {
|
|
10142
|
+
const withoutGitToken = upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", "");
|
|
10143
|
+
return Effect.succeed(clearProjectGitLabels(withoutGitToken));
|
|
10144
|
+
};
|
|
10145
|
+
var updateProjectGitConnect = (spec) => {
|
|
10146
|
+
const tokenKey = buildLabeledEnvKey(gitTokenBaseKey$1, spec.rawLabel);
|
|
10147
|
+
const userKey = buildLabeledEnvKey(gitUserBaseKey, spec.rawLabel);
|
|
10148
|
+
const token = findEnvValue(spec.globalEnvText, tokenKey);
|
|
10149
|
+
if (token === null) return Effect.fail(missingSecret("Git credentials", spec.canonicalLabel, spec.globalEnvPath));
|
|
10150
|
+
const defaultUser = findEnvValue(spec.globalEnvText, gitUserBaseKey) ?? defaultGitUser;
|
|
10151
|
+
const user = findEnvValue(spec.globalEnvText, userKey) ?? defaultUser;
|
|
10152
|
+
const withGitLabel = upsertEnvKey(upsertEnvKey(upsertEnvKey(upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", token), "GIT_AUTH_USER", user), "GH_TOKEN", token), projectGitLabelKey$1, spec.canonicalLabel);
|
|
10153
|
+
return Effect.succeed(upsertEnvKey(withGitLabel, projectGithubLabelKey$1, spec.canonicalLabel));
|
|
10154
|
+
};
|
|
10155
|
+
var updateProjectGitDisconnect = (spec) => {
|
|
10156
|
+
const withoutUser = upsertEnvKey(upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", ""), "GIT_AUTH_USER", "");
|
|
10157
|
+
return Effect.succeed(clearProjectGitLabels(withoutUser));
|
|
10158
|
+
};
|
|
10159
|
+
var resolveAccountCandidates = (authPath, accountLabel) => accountLabel === "default" ? [`${authPath}/default`, authPath] : [`${authPath}/${accountLabel}`];
|
|
10160
|
+
var findFirstCredentialsMatch = (fs, candidates, hasCredentials) => Effect.gen(function* (_) {
|
|
10161
|
+
for (const accountPath of candidates) {
|
|
10162
|
+
if (!(yield* _(fs.exists(accountPath)))) continue;
|
|
10163
|
+
if (yield* _(hasCredentials(fs, accountPath), Effect.orElseSucceed(() => false))) return accountPath;
|
|
10164
|
+
}
|
|
10165
|
+
return null;
|
|
10166
|
+
});
|
|
10167
|
+
var updateProjectClaudeConnect = (spec) => {
|
|
10168
|
+
const accountLabel = normalizeAccountLabel(spec.rawLabel, "default");
|
|
10169
|
+
const accountCandidates = resolveAccountCandidates(spec.claudeAuthPath, accountLabel);
|
|
10170
|
+
return findFirstCredentialsMatch(spec.fs, accountCandidates, hasClaudeAccountCredentials).pipe(Effect.flatMap((matched) => matched === null ? Effect.fail(missingSecret("Claude Code login", spec.canonicalLabel, spec.claudeAuthPath)) : Effect.succeed(upsertEnvKey(spec.projectEnvText, projectClaudeLabelKey$1, spec.canonicalLabel))));
|
|
10171
|
+
};
|
|
10172
|
+
var updateProjectClaudeDisconnect = (spec) => Effect.succeed(upsertEnvKey(spec.projectEnvText, projectClaudeLabelKey$1, ""));
|
|
10173
|
+
var updateProjectGeminiConnect = (spec) => {
|
|
10174
|
+
const accountLabel = normalizeAccountLabel(spec.rawLabel, "default");
|
|
10175
|
+
const accountCandidates = resolveAccountCandidates(spec.geminiAuthPath, accountLabel);
|
|
10176
|
+
return findFirstCredentialsMatch(spec.fs, accountCandidates, hasGeminiAccountCredentials).pipe(Effect.flatMap((matched) => matched === null ? Effect.fail(missingSecret("Gemini CLI API key", spec.canonicalLabel, spec.geminiAuthPath)) : Effect.succeed(upsertEnvKey(spec.projectEnvText, projectGeminiLabelKey$1, spec.canonicalLabel))));
|
|
10177
|
+
};
|
|
10178
|
+
var updateProjectGeminiDisconnect = (spec) => Effect.succeed(upsertEnvKey(spec.projectEnvText, projectGeminiLabelKey$1, ""));
|
|
10179
|
+
var resolveProjectEnvUpdate = (flow, spec) => Match.value(flow).pipe(Match.when("ProjectGithubConnect", () => updateProjectGithubConnect(spec)), Match.when("ProjectGithubDisconnect", () => updateProjectGithubDisconnect(spec)), Match.when("ProjectGitConnect", () => updateProjectGitConnect(spec)), Match.when("ProjectGitDisconnect", () => updateProjectGitDisconnect(spec)), Match.when("ProjectClaudeConnect", () => updateProjectClaudeConnect(spec)), Match.when("ProjectClaudeDisconnect", () => updateProjectClaudeDisconnect(spec)), Match.when("ProjectGeminiConnect", () => updateProjectGeminiConnect(spec)), Match.when("ProjectGeminiDisconnect", () => updateProjectGeminiDisconnect(spec)), Match.exhaustive);
|
|
10180
|
+
//#endregion
|
|
9422
10181
|
//#region src/docker-git/menu-project-auth-data.ts
|
|
9423
10182
|
var projectAuthMenuItems = [
|
|
9424
10183
|
{
|
|
@@ -9445,6 +10204,14 @@ var projectAuthMenuItems = [
|
|
|
9445
10204
|
action: "ProjectClaudeDisconnect",
|
|
9446
10205
|
label: "Project: Claude disconnect"
|
|
9447
10206
|
},
|
|
10207
|
+
{
|
|
10208
|
+
action: "ProjectGeminiConnect",
|
|
10209
|
+
label: "Project: Gemini connect label"
|
|
10210
|
+
},
|
|
10211
|
+
{
|
|
10212
|
+
action: "ProjectGeminiDisconnect",
|
|
10213
|
+
label: "Project: Gemini disconnect"
|
|
10214
|
+
},
|
|
9448
10215
|
{
|
|
9449
10216
|
action: "Refresh",
|
|
9450
10217
|
label: "Refresh snapshot"
|
|
@@ -9475,7 +10242,14 @@ var flowSteps = {
|
|
|
9475
10242
|
required: false,
|
|
9476
10243
|
secret: false
|
|
9477
10244
|
}],
|
|
9478
|
-
ProjectClaudeDisconnect: []
|
|
10245
|
+
ProjectClaudeDisconnect: [],
|
|
10246
|
+
ProjectGeminiConnect: [{
|
|
10247
|
+
key: "label",
|
|
10248
|
+
label: "Label (empty = default)",
|
|
10249
|
+
required: false,
|
|
10250
|
+
secret: false
|
|
10251
|
+
}],
|
|
10252
|
+
ProjectGeminiDisconnect: []
|
|
9479
10253
|
};
|
|
9480
10254
|
var resolveCanonicalLabel = (value) => {
|
|
9481
10255
|
const normalized = normalizeLabel(value);
|
|
@@ -9483,18 +10257,19 @@ var resolveCanonicalLabel = (value) => {
|
|
|
9483
10257
|
};
|
|
9484
10258
|
var githubTokenBaseKey = "GITHUB_TOKEN";
|
|
9485
10259
|
var gitTokenBaseKey = "GIT_AUTH_TOKEN";
|
|
9486
|
-
var gitUserBaseKey = "GIT_AUTH_USER";
|
|
9487
10260
|
var projectGithubLabelKey = "GITHUB_AUTH_LABEL";
|
|
9488
10261
|
var projectGitLabelKey = "GIT_AUTH_LABEL";
|
|
9489
10262
|
var projectClaudeLabelKey = "CLAUDE_AUTH_LABEL";
|
|
9490
|
-
var
|
|
10263
|
+
var projectGeminiLabelKey = "GEMINI_AUTH_LABEL";
|
|
9491
10264
|
var buildGlobalEnvPath = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/env/global.env`;
|
|
9492
10265
|
var buildClaudeAuthPath = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/auth/claude`;
|
|
10266
|
+
var buildGeminiAuthPath = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/auth/gemini`;
|
|
9493
10267
|
var loadProjectAuthEnvText = (project) => Effect.gen(function* (_) {
|
|
9494
10268
|
const fs = yield* _(FileSystem.FileSystem);
|
|
9495
10269
|
const path = yield* _(Path.Path);
|
|
9496
10270
|
const globalEnvPath = buildGlobalEnvPath(process.cwd());
|
|
9497
10271
|
const claudeAuthPath = buildClaudeAuthPath(process.cwd());
|
|
10272
|
+
const geminiAuthPath = buildGeminiAuthPath(process.cwd());
|
|
9498
10273
|
yield* _(ensureEnvFile$1(fs, path, globalEnvPath));
|
|
9499
10274
|
yield* _(ensureEnvFile$1(fs, path, project.envProjectPath));
|
|
9500
10275
|
const globalEnvText = yield* _(readEnvText(fs, globalEnvPath));
|
|
@@ -9505,69 +10280,29 @@ var loadProjectAuthEnvText = (project) => Effect.gen(function* (_) {
|
|
|
9505
10280
|
globalEnvPath,
|
|
9506
10281
|
projectEnvPath: project.envProjectPath,
|
|
9507
10282
|
claudeAuthPath,
|
|
10283
|
+
geminiAuthPath,
|
|
9508
10284
|
globalEnvText,
|
|
9509
10285
|
projectEnvText
|
|
9510
10286
|
};
|
|
9511
10287
|
});
|
|
9512
|
-
var readProjectAuthSnapshot = (project) => pipe(loadProjectAuthEnvText(project), Effect.flatMap(({ claudeAuthPath, fs, globalEnvPath, globalEnvText, path, projectEnvPath, projectEnvText }) =>
|
|
10288
|
+
var readProjectAuthSnapshot = (project) => pipe(loadProjectAuthEnvText(project), Effect.flatMap(({ claudeAuthPath, fs, geminiAuthPath, globalEnvPath, globalEnvText, path, projectEnvPath, projectEnvText }) => countAuthAccountEntries(fs, path, claudeAuthPath, geminiAuthPath).pipe(Effect.map(({ claudeAuthEntries, geminiAuthEntries }) => ({
|
|
9513
10289
|
projectDir: project.projectDir,
|
|
9514
10290
|
projectName: project.displayName,
|
|
9515
10291
|
envGlobalPath: globalEnvPath,
|
|
9516
10292
|
envProjectPath: projectEnvPath,
|
|
9517
10293
|
claudeAuthPath,
|
|
10294
|
+
geminiAuthPath,
|
|
9518
10295
|
githubTokenEntries: countKeyEntries(globalEnvText, githubTokenBaseKey),
|
|
9519
10296
|
gitTokenEntries: countKeyEntries(globalEnvText, gitTokenBaseKey),
|
|
9520
10297
|
claudeAuthEntries,
|
|
10298
|
+
geminiAuthEntries,
|
|
9521
10299
|
activeGithubLabel: findEnvValue(projectEnvText, projectGithubLabelKey),
|
|
9522
10300
|
activeGitLabel: findEnvValue(projectEnvText, projectGitLabelKey),
|
|
9523
|
-
activeClaudeLabel: findEnvValue(projectEnvText, projectClaudeLabelKey)
|
|
10301
|
+
activeClaudeLabel: findEnvValue(projectEnvText, projectClaudeLabelKey),
|
|
10302
|
+
activeGeminiLabel: findEnvValue(projectEnvText, projectGeminiLabelKey)
|
|
9524
10303
|
})))));
|
|
9525
|
-
var
|
|
9526
|
-
var
|
|
9527
|
-
const key = buildLabeledEnvKey(githubTokenBaseKey, spec.rawLabel);
|
|
9528
|
-
const token = findEnvValue(spec.globalEnvText, key);
|
|
9529
|
-
if (token === null) return Effect.fail(missingSecret("GitHub token", spec.canonicalLabel, spec.globalEnvPath));
|
|
9530
|
-
const withoutGitLabel = upsertEnvKey(upsertEnvKey(upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", token), "GH_TOKEN", token), projectGitLabelKey, "");
|
|
9531
|
-
return Effect.succeed(upsertEnvKey(withoutGitLabel, projectGithubLabelKey, spec.canonicalLabel));
|
|
9532
|
-
};
|
|
9533
|
-
var clearProjectGitLabels = (envText) => {
|
|
9534
|
-
return upsertEnvKey(upsertEnvKey(upsertEnvKey(envText, "GH_TOKEN", ""), projectGitLabelKey, ""), projectGithubLabelKey, "");
|
|
9535
|
-
};
|
|
9536
|
-
var updateProjectGithubDisconnect = (spec) => {
|
|
9537
|
-
const withoutGitToken = upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", "");
|
|
9538
|
-
return Effect.succeed(clearProjectGitLabels(withoutGitToken));
|
|
9539
|
-
};
|
|
9540
|
-
var updateProjectGitConnect = (spec) => {
|
|
9541
|
-
const tokenKey = buildLabeledEnvKey(gitTokenBaseKey, spec.rawLabel);
|
|
9542
|
-
const userKey = buildLabeledEnvKey(gitUserBaseKey, spec.rawLabel);
|
|
9543
|
-
const token = findEnvValue(spec.globalEnvText, tokenKey);
|
|
9544
|
-
if (token === null) return Effect.fail(missingSecret("Git credentials", spec.canonicalLabel, spec.globalEnvPath));
|
|
9545
|
-
const defaultUser = findEnvValue(spec.globalEnvText, gitUserBaseKey) ?? defaultGitUser;
|
|
9546
|
-
const user = findEnvValue(spec.globalEnvText, userKey) ?? defaultUser;
|
|
9547
|
-
const withGitLabel = upsertEnvKey(upsertEnvKey(upsertEnvKey(upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", token), "GIT_AUTH_USER", user), "GH_TOKEN", token), projectGitLabelKey, spec.canonicalLabel);
|
|
9548
|
-
return Effect.succeed(upsertEnvKey(withGitLabel, projectGithubLabelKey, spec.canonicalLabel));
|
|
9549
|
-
};
|
|
9550
|
-
var updateProjectGitDisconnect = (spec) => {
|
|
9551
|
-
const withoutUser = upsertEnvKey(upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", ""), "GIT_AUTH_USER", "");
|
|
9552
|
-
return Effect.succeed(clearProjectGitLabels(withoutUser));
|
|
9553
|
-
};
|
|
9554
|
-
var resolveClaudeAccountCandidates = (claudeAuthPath, accountLabel) => accountLabel === "default" ? [`${claudeAuthPath}/default`, claudeAuthPath] : [`${claudeAuthPath}/${accountLabel}`];
|
|
9555
|
-
var updateProjectClaudeConnect = (spec) => {
|
|
9556
|
-
const accountLabel = normalizeAccountLabel(spec.rawLabel, "default");
|
|
9557
|
-
const accountCandidates = resolveClaudeAccountCandidates(spec.claudeAuthPath, accountLabel);
|
|
9558
|
-
return Effect.gen(function* (_) {
|
|
9559
|
-
for (const accountPath of accountCandidates) {
|
|
9560
|
-
if (!(yield* _(spec.fs.exists(accountPath)))) continue;
|
|
9561
|
-
if (yield* _(hasClaudeAccountCredentials(spec.fs, accountPath), Effect.orElseSucceed(() => false))) return upsertEnvKey(spec.projectEnvText, projectClaudeLabelKey, spec.canonicalLabel);
|
|
9562
|
-
}
|
|
9563
|
-
return yield* _(Effect.fail(missingSecret("Claude Code login", spec.canonicalLabel, spec.claudeAuthPath)));
|
|
9564
|
-
});
|
|
9565
|
-
};
|
|
9566
|
-
var updateProjectClaudeDisconnect = (spec) => {
|
|
9567
|
-
return Effect.succeed(upsertEnvKey(spec.projectEnvText, projectClaudeLabelKey, ""));
|
|
9568
|
-
};
|
|
9569
|
-
var resolveProjectEnvUpdate = (flow, spec) => Match.value(flow).pipe(Match.when("ProjectGithubConnect", () => updateProjectGithubConnect(spec)), Match.when("ProjectGithubDisconnect", () => updateProjectGithubDisconnect(spec)), Match.when("ProjectGitConnect", () => updateProjectGitConnect(spec)), Match.when("ProjectGitDisconnect", () => updateProjectGitDisconnect(spec)), Match.when("ProjectClaudeConnect", () => updateProjectClaudeConnect(spec)), Match.when("ProjectClaudeDisconnect", () => updateProjectClaudeDisconnect(spec)), Match.exhaustive);
|
|
9570
|
-
var writeProjectAuthFlow = (project, flow, values) => pipe(loadProjectAuthEnvText(project), Effect.flatMap(({ claudeAuthPath, fs, globalEnvPath, globalEnvText, projectEnvPath, projectEnvText }) => {
|
|
10304
|
+
var resolveSyncMessage = (flow, canonicalLabel, displayName) => Match.value(flow).pipe(Match.when("ProjectGithubConnect", () => `chore(state): project auth gh ${canonicalLabel} ${displayName}`), Match.when("ProjectGithubDisconnect", () => `chore(state): project auth gh logout ${displayName}`), Match.when("ProjectGitConnect", () => `chore(state): project auth git ${canonicalLabel} ${displayName}`), Match.when("ProjectGitDisconnect", () => `chore(state): project auth git logout ${displayName}`), Match.when("ProjectClaudeConnect", () => `chore(state): project auth claude ${canonicalLabel} ${displayName}`), Match.when("ProjectClaudeDisconnect", () => `chore(state): project auth claude logout ${displayName}`), Match.when("ProjectGeminiConnect", () => `chore(state): project auth gemini ${canonicalLabel} ${displayName}`), Match.when("ProjectGeminiDisconnect", () => `chore(state): project auth gemini logout ${displayName}`), Match.exhaustive);
|
|
10305
|
+
var writeProjectAuthFlow = (project, flow, values) => pipe(loadProjectAuthEnvText(project), Effect.flatMap(({ claudeAuthPath, fs, geminiAuthPath, globalEnvPath, globalEnvText, projectEnvPath, projectEnvText }) => {
|
|
9571
10306
|
const rawLabel = values["label"] ?? "";
|
|
9572
10307
|
const canonicalLabel = resolveCanonicalLabel(rawLabel);
|
|
9573
10308
|
const nextProjectEnv = resolveProjectEnvUpdate(flow, {
|
|
@@ -9577,9 +10312,10 @@ var writeProjectAuthFlow = (project, flow, values) => pipe(loadProjectAuthEnvTex
|
|
|
9577
10312
|
globalEnvPath,
|
|
9578
10313
|
globalEnvText,
|
|
9579
10314
|
projectEnvText,
|
|
9580
|
-
claudeAuthPath
|
|
10315
|
+
claudeAuthPath,
|
|
10316
|
+
geminiAuthPath
|
|
9581
10317
|
});
|
|
9582
|
-
const syncMessage =
|
|
10318
|
+
const syncMessage = resolveSyncMessage(flow, canonicalLabel, project.displayName);
|
|
9583
10319
|
return pipe(nextProjectEnv, Effect.flatMap((nextText) => fs.writeFileString(projectEnvPath, nextText)), Effect.zipRight(autoSyncState(syncMessage)));
|
|
9584
10320
|
}), Effect.asVoid);
|
|
9585
10321
|
var projectAuthViewSteps = (flow) => flowSteps[flow];
|
|
@@ -9615,7 +10351,7 @@ var startProjectAuthPrompt = (project, snapshot, flow, context) => {
|
|
|
9615
10351
|
var loadProjectAuthMenuView = (project, context) => pipe(readProjectAuthSnapshot(project), Effect.tap((snapshot) => Effect.sync(() => {
|
|
9616
10352
|
startProjectAuthMenu(project, snapshot, context);
|
|
9617
10353
|
})), Effect.asVoid);
|
|
9618
|
-
var successMessage = (flow, label) => Match.value(flow).pipe(Match.when("ProjectGithubConnect", () => `Connected GitHub label (${label}) to project.`), Match.when("ProjectGithubDisconnect", () => "Disconnected GitHub from project."), Match.when("ProjectGitConnect", () => `Connected Git label (${label}) to project.`), Match.when("ProjectGitDisconnect", () => "Disconnected Git from project."), Match.when("ProjectClaudeConnect", () => `Connected Claude label (${label}) to project.`), Match.when("ProjectClaudeDisconnect", () => "Disconnected Claude from project."), Match.exhaustive);
|
|
10354
|
+
var successMessage = (flow, label) => Match.value(flow).pipe(Match.when("ProjectGithubConnect", () => `Connected GitHub label (${label}) to project.`), Match.when("ProjectGithubDisconnect", () => "Disconnected GitHub from project."), Match.when("ProjectGitConnect", () => `Connected Git label (${label}) to project.`), Match.when("ProjectGitDisconnect", () => "Disconnected Git from project."), Match.when("ProjectClaudeConnect", () => `Connected Claude label (${label}) to project.`), Match.when("ProjectClaudeDisconnect", () => "Disconnected Claude from project."), Match.when("ProjectGeminiConnect", () => `Connected Gemini label (${label}) to project.`), Match.when("ProjectGeminiDisconnect", () => "Disconnected Gemini from project."), Match.exhaustive);
|
|
9619
10355
|
var runProjectAuthEffect = (project, flow, values, label, context) => {
|
|
9620
10356
|
context.runner.runEffect(pipe(writeProjectAuthFlow(project, flow, values), Effect.zipRight(readProjectAuthSnapshot(project)), Effect.tap((snapshot) => Effect.sync(() => {
|
|
9621
10357
|
startProjectAuthMenu(project, snapshot, context);
|
|
@@ -9640,7 +10376,7 @@ var runProjectAuthAction$1 = (action, view, context) => {
|
|
|
9640
10376
|
context.runner.runEffect(loadProjectAuthMenuView(view.project, context));
|
|
9641
10377
|
return;
|
|
9642
10378
|
}
|
|
9643
|
-
if (action === "ProjectGithubDisconnect" || action === "ProjectGitDisconnect" || action === "ProjectClaudeDisconnect") {
|
|
10379
|
+
if (action === "ProjectGithubDisconnect" || action === "ProjectGitDisconnect" || action === "ProjectClaudeDisconnect" || action === "ProjectGeminiDisconnect") {
|
|
9644
10380
|
runProjectAuthEffect(view.project, action, {}, "default", context);
|
|
9645
10381
|
return;
|
|
9646
10382
|
}
|
|
@@ -10810,7 +11546,7 @@ var setExitCode = (code) => Effect.sync(() => {
|
|
|
10810
11546
|
});
|
|
10811
11547
|
var logWarningAndExit = (error) => pipe(Effect.logWarning(renderError(error)), Effect.tap(() => setExitCode(1)), Effect.asVoid);
|
|
10812
11548
|
var logErrorAndExit = (error) => pipe(Effect.logError(renderError(error)), Effect.tap(() => setExitCode(1)), Effect.asVoid);
|
|
10813
|
-
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)), Match.when({ _tag: "
|
|
11549
|
+
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);
|
|
10814
11550
|
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({
|
|
10815
11551
|
onFailure: (error) => isParseError(error) ? logErrorAndExit(error) : pipe(Effect.logError(renderError(error)), Effect.flatMap(() => Effect.fail(error))),
|
|
10816
11552
|
onSuccess: () => Effect.void
|