@prover-coder-ai/docker-git 1.0.43 → 1.0.44

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.
@@ -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$1 = (fs, filePath) => Effect.gen(function* (_) {
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$1(fs, `${accountPath}/.credentials.json`))) return true;
896
- if (yield* _(isRegularFile$1(fs, `${accountPath}/.claude/.credentials.json`))) return true;
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 }),
@@ -2969,6 +2978,143 @@ if [[ -f "$LEGACY_AGENTS_PATH" && -f "$AGENTS_PATH" ]]; then
2969
2978
  fi`;
2970
2979
  var renderEntrypointAgentsNotice = (config) => entrypointAgentsNoticeTemplate.replaceAll("__CODEX_HOME__", config.codexHome).replaceAll("__SSH_USER__", config.sshUser).replaceAll("__TARGET_DIR__", config.targetDir);
2971
2980
  //#endregion
2981
+ //#region ../lib/src/core/templates-entrypoint/gemini.ts
2982
+ var geminiAuthRootContainerPath = (sshUser) => `/home/${sshUser}/.docker-git/.orch/auth/gemini`;
2983
+ var geminiAuthConfigTemplate = String.raw`# Gemini CLI: expose GEMINI_API_KEY for SSH sessions (API key stored under ~/.docker-git/.orch/auth/gemini)
2984
+ GEMINI_LABEL_RAW="${"$"}{GEMINI_AUTH_LABEL:-}"
2985
+ if [[ -z "$GEMINI_LABEL_RAW" ]]; then
2986
+ GEMINI_LABEL_RAW="default"
2987
+ fi
2988
+
2989
+ GEMINI_LABEL_NORM="$(printf "%s" "$GEMINI_LABEL_RAW" \
2990
+ | tr '[:upper:]' '[:lower:]' \
2991
+ | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//')"
2992
+ if [[ -z "$GEMINI_LABEL_NORM" ]]; then
2993
+ GEMINI_LABEL_NORM="default"
2994
+ fi
2995
+
2996
+ GEMINI_AUTH_ROOT="__GEMINI_AUTH_ROOT__"
2997
+ GEMINI_AUTH_DIR="$GEMINI_AUTH_ROOT/$GEMINI_LABEL_NORM"
2998
+
2999
+ # Backward compatibility: if default auth is stored directly under gemini root, reuse it.
3000
+ if [[ "$GEMINI_LABEL_NORM" == "default" ]]; then
3001
+ GEMINI_ROOT_ENV_FILE="$GEMINI_AUTH_ROOT/.env"
3002
+ if [[ -f "$GEMINI_ROOT_ENV_FILE" ]]; then
3003
+ GEMINI_AUTH_DIR="$GEMINI_AUTH_ROOT"
3004
+ fi
3005
+ fi
3006
+
3007
+ mkdir -p "$GEMINI_AUTH_DIR" || true
3008
+ GEMINI_HOME_DIR="__GEMINI_HOME_DIR__"
3009
+ mkdir -p "$GEMINI_HOME_DIR" || true
3010
+
3011
+ GEMINI_API_KEY_FILE="$GEMINI_AUTH_DIR/.api-key"
3012
+ GEMINI_ENV_FILE="$GEMINI_AUTH_DIR/.env"
3013
+ GEMINI_HOME_ENV_FILE="$GEMINI_HOME_DIR/.env"
3014
+
3015
+ docker_git_link_gemini_file() {
3016
+ local source_path="$1"
3017
+ local link_path="$2"
3018
+
3019
+ # Preserve user-created regular files and seed config dir once.
3020
+ if [[ -e "$link_path" && ! -L "$link_path" ]]; then
3021
+ if [[ -f "$link_path" && ! -e "$source_path" ]]; then
3022
+ cp "$link_path" "$source_path" || true
3023
+ chmod 0600 "$source_path" || true
3024
+ fi
3025
+ return 0
3026
+ fi
3027
+
3028
+ ln -sfn "$source_path" "$link_path" || true
3029
+ }
3030
+
3031
+ # Link Gemini .env file from auth dir to home dir
3032
+ docker_git_link_gemini_file "$GEMINI_ENV_FILE" "$GEMINI_HOME_ENV_FILE"
3033
+
3034
+ docker_git_refresh_gemini_api_key() {
3035
+ local api_key=""
3036
+ # Try to read from dedicated API key file first
3037
+ if [[ -f "$GEMINI_API_KEY_FILE" ]]; then
3038
+ api_key="$(tr -d '\r\n' < "$GEMINI_API_KEY_FILE")"
3039
+ fi
3040
+ # Fall back to .env file
3041
+ if [[ -z "$api_key" && -f "$GEMINI_ENV_FILE" ]]; then
3042
+ api_key="$(grep -E '^GEMINI_API_KEY=' "$GEMINI_ENV_FILE" 2>/dev/null | head -1 | cut -d'=' -f2- | tr -d '\r\n' | sed "s/^['\"]//;s/['\"]$//")"
3043
+ fi
3044
+ if [[ -n "$api_key" ]]; then
3045
+ export GEMINI_API_KEY="$api_key"
3046
+ else
3047
+ unset GEMINI_API_KEY || true
3048
+ fi
3049
+ }
3050
+
3051
+ docker_git_refresh_gemini_api_key`;
3052
+ var renderGeminiAuthConfig = (config) => geminiAuthConfigTemplate.replaceAll("__GEMINI_AUTH_ROOT__", geminiAuthRootContainerPath(config.sshUser)).replaceAll("__GEMINI_HOME_DIR__", `/home/${config.sshUser}/.gemini`);
3053
+ var renderGeminiCliInstall = () => String.raw`# Gemini CLI: ensure CLI command exists (non-blocking startup self-heal)
3054
+ docker_git_ensure_gemini_cli() {
3055
+ if command -v gemini >/dev/null 2>&1; then
3056
+ return 0
3057
+ fi
3058
+
3059
+ if ! command -v npm >/dev/null 2>&1; then
3060
+ return 0
3061
+ fi
3062
+
3063
+ NPM_ROOT="$(npm root -g 2>/dev/null || true)"
3064
+ GEMINI_CLI_JS="$NPM_ROOT/@google/gemini-cli/build/cli.js"
3065
+ if [[ -z "$NPM_ROOT" || ! -f "$GEMINI_CLI_JS" ]]; then
3066
+ echo "docker-git: gemini cli.js not found under npm global root; skip shim restore" >&2
3067
+ return 0
3068
+ fi
3069
+
3070
+ # Rebuild a minimal shim when npm package exists but binary link is missing.
3071
+ cat <<'EOF' > /usr/local/bin/gemini
3072
+ #!/usr/bin/env bash
3073
+ set -euo pipefail
3074
+
3075
+ if ! command -v npm >/dev/null 2>&1; then
3076
+ echo "gemini: npm is required but missing" >&2
3077
+ exit 127
3078
+ fi
3079
+
3080
+ NPM_ROOT="$(npm root -g 2>/dev/null || true)"
3081
+ GEMINI_CLI_JS="$NPM_ROOT/@google/gemini-cli/build/cli.js"
3082
+ if [[ -z "$NPM_ROOT" || ! -f "$GEMINI_CLI_JS" ]]; then
3083
+ echo "gemini: cli.js not found under npm global root" >&2
3084
+ exit 127
3085
+ fi
3086
+
3087
+ exec node "$GEMINI_CLI_JS" "$@"
3088
+ EOF
3089
+ chmod 0755 /usr/local/bin/gemini || true
3090
+ ln -sf /usr/local/bin/gemini /usr/bin/gemini || true
3091
+ }
3092
+
3093
+ docker_git_ensure_gemini_cli`;
3094
+ var renderGeminiProfileSetup = () => String.raw`GEMINI_PROFILE="/etc/profile.d/gemini-config.sh"
3095
+ printf "export GEMINI_AUTH_LABEL=%q\n" "${"$"}{GEMINI_AUTH_LABEL:-default}" > "$GEMINI_PROFILE"
3096
+ cat <<'EOF' >> "$GEMINI_PROFILE"
3097
+ GEMINI_API_KEY_FILE="${"$"}{GEMINI_AUTH_DIR:-$HOME/.gemini}/.api-key"
3098
+ GEMINI_ENV_FILE="${"$"}{GEMINI_AUTH_DIR:-$HOME/.gemini}/.env"
3099
+ if [[ -f "$GEMINI_API_KEY_FILE" ]]; then
3100
+ export GEMINI_API_KEY="$(tr -d '\r\n' < "$GEMINI_API_KEY_FILE")"
3101
+ elif [[ -f "$GEMINI_ENV_FILE" ]]; then
3102
+ GEMINI_KEY="$(grep -E '^GEMINI_API_KEY=' "$GEMINI_ENV_FILE" 2>/dev/null | head -1 | cut -d'=' -f2- | tr -d '\r\n' | sed "s/^['\"]//;s/['\"]$//")"
3103
+ if [[ -n "$GEMINI_KEY" ]]; then
3104
+ export GEMINI_API_KEY="$GEMINI_KEY"
3105
+ fi
3106
+ fi
3107
+ EOF
3108
+ chmod 0644 "$GEMINI_PROFILE" || true
3109
+
3110
+ docker_git_upsert_ssh_env "GEMINI_AUTH_LABEL" "${"$"}{GEMINI_AUTH_LABEL:-default}"
3111
+ docker_git_upsert_ssh_env "GEMINI_API_KEY" "${"$"}{GEMINI_API_KEY:-}"`;
3112
+ var renderEntrypointGeminiConfig = (config) => [
3113
+ renderGeminiAuthConfig(config),
3114
+ renderGeminiCliInstall(),
3115
+ renderGeminiProfileSetup()
3116
+ ].join("\n\n");
3117
+ //#endregion
2972
3118
  //#region ../lib/src/core/templates-entrypoint/git.ts
2973
3119
  var renderAuthLabelResolution = () => String.raw`# 2) Ensure GitHub auth vars are available for SSH sessions.
2974
3120
  # Prefer a label-selected token (same selection model as clone/create) when present.
@@ -3871,6 +4017,7 @@ var renderEntrypoint = (config) => [
3871
4017
  renderEntrypointDockerSocket(config),
3872
4018
  renderEntrypointGitConfig(config),
3873
4019
  renderEntrypointClaudeConfig(config),
4020
+ renderEntrypointGeminiConfig(config),
3874
4021
  renderEntrypointGitHooks(),
3875
4022
  renderEntrypointBackgroundTasks(config),
3876
4023
  renderEntrypointBaseline(),
@@ -4427,7 +4574,7 @@ var resolveOriginPushTarget = (originUrl) => {
4427
4574
  const trimmed = originUrl?.trim() ?? "";
4428
4575
  return trimmed.length > 0 ? trimmed : "origin";
4429
4576
  };
4430
- var resolveSyncMessage = (value) => {
4577
+ var resolveSyncMessage$1 = (value) => {
4431
4578
  const trimmed = value?.trim() ?? "";
4432
4579
  return trimmed.length > 0 ? trimmed : defaultSyncMessage;
4433
4580
  };
@@ -4508,7 +4655,7 @@ var runStateSyncOps = (root, originUrl, message, env, options) => Effect.gen(fun
4508
4655
  yield* _(normalizeLegacyStateProjects(root));
4509
4656
  const baseBranch = resolveBaseBranch(yield* _(getCurrentBranch(root, env)));
4510
4657
  yield* _(pullRemoteAndRestoreLocal(root, baseBranch, env));
4511
- yield* _(commitAllIfNeeded(root, resolveSyncMessage(message), env));
4658
+ yield* _(commitAllIfNeeded(root, resolveSyncMessage$1(message), env));
4512
4659
  const pushExit = yield* _(gitExitCode(root, [
4513
4660
  "push",
4514
4661
  "--no-verify",
@@ -5921,12 +6068,7 @@ var applyProjectConfig = (command) => runApplyForProjectDir(command.projectDir,
5921
6068
  return yield* _(runApplyForProjectDir(inferredProjectDir, command));
5922
6069
  }) : Effect.fail(error)));
5923
6070
  //#endregion
5924
- //#region ../lib/src/usecases/auth-claude-oauth.ts
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;
6071
+ //#region ../lib/src/shell/ansi-strip.ts
5930
6072
  var ansiEscape = "\x1B";
5931
6073
  var ansiBell = "\x07";
5932
6074
  var isAnsiFinalByte = (codePoint) => codePoint !== void 0 && codePoint >= 64 && codePoint <= 126;
@@ -5970,6 +6112,20 @@ var stripAnsi = (raw) => {
5970
6112
  }
5971
6113
  return cleaned.join("");
5972
6114
  };
6115
+ var writeChunkToFd = (fd, chunk) => {
6116
+ if (fd === 2) {
6117
+ process.stderr.write(chunk);
6118
+ return;
6119
+ }
6120
+ process.stdout.write(chunk);
6121
+ };
6122
+ //#endregion
6123
+ //#region ../lib/src/usecases/auth-claude-oauth.ts
6124
+ var oauthTokenEnvKey = "DOCKER_GIT_CLAUDE_OAUTH_TOKEN";
6125
+ var tokenMarker = "Your OAuth token (valid for 1 year):";
6126
+ var tokenFooterMarker = "Store this token securely.";
6127
+ var outputWindowSize$1 = 262144;
6128
+ var oauthTokenRegex = /([A-Za-z0-9][A-Za-z0-9._-]{20,})/u;
5973
6129
  var extractOauthToken = (rawOutput) => {
5974
6130
  const normalized = stripAnsi(rawOutput).replaceAll("\r", "\n");
5975
6131
  const markerIndex = normalized.lastIndexOf(tokenMarker);
@@ -6024,21 +6180,14 @@ var buildDockerSetupTokenArgs = (spec) => {
6024
6180
  ...spec.args
6025
6181
  ];
6026
6182
  };
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 writeChunkToFd = (fd, chunk) => {
6029
- if (fd === 2) {
6030
- process.stderr.write(chunk);
6031
- return;
6032
- }
6033
- process.stdout.write(chunk);
6034
- };
6035
- var pumpDockerOutput = (source, fd, tokenBox) => {
6183
+ 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")));
6184
+ var pumpDockerOutput$1 = (source, fd, tokenBox) => {
6036
6185
  const decoder = new TextDecoder("utf-8");
6037
6186
  let outputWindow = "";
6038
6187
  return pipe(source, Stream.runForEach((chunk) => Effect.sync(() => {
6039
6188
  writeChunkToFd(fd, chunk);
6040
6189
  outputWindow += decoder.decode(chunk);
6041
- if (outputWindow.length > outputWindowSize) outputWindow = outputWindow.slice(-outputWindowSize);
6190
+ if (outputWindow.length > outputWindowSize$1) outputWindow = outputWindow.slice(-outputWindowSize$1);
6042
6191
  if (tokenBox.value !== null) return;
6043
6192
  const parsed = extractOauthToken(outputWindow);
6044
6193
  if (parsed !== null) tokenBox.value = parsed;
@@ -6060,10 +6209,10 @@ var runClaudeOauthLoginWithPrompt = (cwd, accountPath, options) => {
6060
6209
  const envToken = oauthTokenFromEnv();
6061
6210
  if (envToken !== null) return ensureOauthToken(envToken);
6062
6211
  return Effect.scoped(Effect.gen(function* (_) {
6063
- const proc = yield* _(startDockerProcess(yield* _(CommandExecutor.CommandExecutor), buildDockerSetupTokenSpec(cwd, yield* _(resolveDockerVolumeHostPath(cwd, accountPath)), options.image, options.containerPath)));
6212
+ const proc = yield* _(startDockerProcess$1(yield* _(CommandExecutor.CommandExecutor), buildDockerSetupTokenSpec(cwd, yield* _(resolveDockerVolumeHostPath(cwd, accountPath)), options.image, options.containerPath)));
6064
6213
  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)));
6214
+ const stdoutFiber = yield* _(Effect.forkScoped(pumpDockerOutput$1(proc.stdout, 1, tokenBox)));
6215
+ const stderrFiber = yield* _(Effect.forkScoped(pumpDockerOutput$1(proc.stderr, 2, tokenBox)));
6067
6216
  const exitCode = yield* _(proc.exitCode.pipe(Effect.map(Number)));
6068
6217
  yield* _(Fiber$1.join(stdoutFiber));
6069
6218
  yield* _(Fiber$1.join(stderrFiber));
@@ -6084,19 +6233,15 @@ var claudeOauthTokenPath = (accountPath) => `${accountPath}/${claudeOauthTokenFi
6084
6233
  var claudeConfigPath = (accountPath) => `${accountPath}/${claudeConfigFileName}`;
6085
6234
  var claudeCredentialsPath = (accountPath) => `${accountPath}/${claudeCredentialsFileName}`;
6086
6235
  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
6236
  var syncClaudeCredentialsFile = (fs, accountPath) => Effect.gen(function* (_) {
6092
6237
  const nestedPath = claudeNestedCredentialsPath(accountPath);
6093
6238
  const rootPath = claudeCredentialsPath(accountPath);
6094
- if (yield* _(isRegularFile(fs, nestedPath))) {
6239
+ if (yield* _(isRegularFile$1(fs, nestedPath))) {
6095
6240
  yield* _(fs.copyFile(nestedPath, rootPath));
6096
6241
  yield* _(fs.chmod(rootPath, 384), Effect.orElseSucceed(() => void 0));
6097
6242
  return;
6098
6243
  }
6099
- if (yield* _(isRegularFile(fs, rootPath))) {
6244
+ if (yield* _(isRegularFile$1(fs, rootPath))) {
6100
6245
  const nestedDirPath = `${accountPath}/${claudeCredentialsDirName}`;
6101
6246
  yield* _(fs.makeDirectory(nestedDirPath, { recursive: true }));
6102
6247
  yield* _(fs.copyFile(rootPath, nestedPath));
@@ -6109,12 +6254,12 @@ var clearClaudeSessionCredentials = (fs, accountPath) => Effect.gen(function* (_
6109
6254
  });
6110
6255
  var hasNonEmptyOauthToken$1 = (fs, accountPath) => Effect.gen(function* (_) {
6111
6256
  const tokenPath = claudeOauthTokenPath(accountPath);
6112
- if (!(yield* _(isRegularFile(fs, tokenPath)))) return false;
6257
+ if (!(yield* _(isRegularFile$1(fs, tokenPath)))) return false;
6113
6258
  return (yield* _(fs.readFileString(tokenPath), Effect.orElseSucceed(() => ""))).trim().length > 0;
6114
6259
  });
6115
6260
  var readOauthToken = (fs, accountPath) => Effect.gen(function* (_) {
6116
6261
  const tokenPath = claudeOauthTokenPath(accountPath);
6117
- if (!(yield* _(isRegularFile(fs, tokenPath)))) return null;
6262
+ if (!(yield* _(isRegularFile$1(fs, tokenPath)))) return null;
6118
6263
  const token = (yield* _(fs.readFileString(tokenPath), Effect.orElseSucceed(() => ""))).trim();
6119
6264
  return token.length > 0 ? token : null;
6120
6265
  });
@@ -6124,7 +6269,7 @@ var resolveClaudeAuthMethod = (fs, accountPath) => Effect.gen(function* (_) {
6124
6269
  return "oauth-token";
6125
6270
  }
6126
6271
  yield* _(syncClaudeCredentialsFile(fs, accountPath));
6127
- return (yield* _(isRegularFile(fs, claudeCredentialsPath(accountPath)))) ? "claude-ai-session" : "none";
6272
+ return (yield* _(isRegularFile$1(fs, claudeCredentialsPath(accountPath)))) ? "claude-ai-session" : "none";
6128
6273
  });
6129
6274
  var buildClaudeAuthEnv = (interactive, oauthToken = null) => [...interactive ? [
6130
6275
  `HOME=${claudeContainerHomeDir}`,
@@ -6349,6 +6494,259 @@ var authCodexStatus = (command) => withCodexAuth(command, ({ accountPath, cwd })
6349
6494
  }));
6350
6495
  var authCodexLogout = (command) => withCodexAuth(command, ({ accountPath, cwd }) => runCodexLogout(cwd, accountPath)).pipe(Effect.zipRight(autoSyncState(`chore(state): auth codex logout ${normalizeAccountLabel(command.label, "default")}`)));
6351
6496
  //#endregion
6497
+ //#region ../lib/src/usecases/auth-gemini-oauth.ts
6498
+ var outputWindowSize = 262144;
6499
+ var authSuccessPatterns = [
6500
+ "Authentication succeeded",
6501
+ "Authentication successful",
6502
+ "Successfully authenticated",
6503
+ "Logged in as",
6504
+ "You are now logged in"
6505
+ ];
6506
+ var authFailurePatterns = [
6507
+ "Authentication failed",
6508
+ "Failed to authenticate",
6509
+ "Authorization failed",
6510
+ "Authentication timed out",
6511
+ "Authentication cancelled"
6512
+ ];
6513
+ var detectAuthResult = (output) => {
6514
+ const normalized = stripAnsi(output).toLowerCase();
6515
+ for (const pattern of authSuccessPatterns) if (normalized.includes(pattern.toLowerCase())) return "success";
6516
+ for (const pattern of authFailurePatterns) if (normalized.includes(pattern.toLowerCase())) return "failure";
6517
+ return "pending";
6518
+ };
6519
+ var geminiOauthCallbackPort = 38751;
6520
+ var buildDockerGeminiAuthSpec = (cwd, accountPath, image, containerPath) => ({
6521
+ cwd,
6522
+ image,
6523
+ hostPath: accountPath,
6524
+ containerPath,
6525
+ callbackPort: geminiOauthCallbackPort,
6526
+ env: [
6527
+ `HOME=${containerPath}`,
6528
+ "NO_BROWSER=true",
6529
+ "GEMINI_CLI_NONINTERACTIVE=false",
6530
+ `OAUTH_CALLBACK_PORT=${geminiOauthCallbackPort}`,
6531
+ "OAUTH_CALLBACK_HOST=0.0.0.0"
6532
+ ]
6533
+ });
6534
+ var buildDockerGeminiAuthArgs = (spec) => {
6535
+ const base = [
6536
+ "run",
6537
+ "--rm",
6538
+ "-i",
6539
+ "-t",
6540
+ "-v",
6541
+ `${spec.hostPath}:${spec.containerPath}`,
6542
+ "-p",
6543
+ `${spec.callbackPort}:${spec.callbackPort}`
6544
+ ];
6545
+ const dockerUser = resolveDefaultDockerUser();
6546
+ if (dockerUser !== null) base.push("--user", dockerUser);
6547
+ for (const entry of spec.env) {
6548
+ const trimmed = entry.trim();
6549
+ if (trimmed.length === 0) continue;
6550
+ base.push("-e", trimmed);
6551
+ }
6552
+ return [
6553
+ ...base,
6554
+ spec.image,
6555
+ "gemini",
6556
+ "--debug"
6557
+ ];
6558
+ };
6559
+ var startDockerProcess = (executor, spec) => executor.start(pipe(Command.make("docker", ...buildDockerGeminiAuthArgs(spec)), Command.workingDirectory(spec.cwd), Command.stdin("inherit"), Command.stdout("pipe"), Command.stderr("pipe")));
6560
+ var pumpDockerOutput = (source, fd, resultBox) => {
6561
+ const decoder = new TextDecoder("utf-8");
6562
+ let outputWindow = "";
6563
+ return pipe(source, Stream.runForEach((chunk) => Effect.sync(() => {
6564
+ writeChunkToFd(fd, chunk);
6565
+ outputWindow += decoder.decode(chunk);
6566
+ if (outputWindow.length > outputWindowSize) outputWindow = outputWindow.slice(-outputWindowSize);
6567
+ if (resultBox.value !== "pending") return;
6568
+ const result = detectAuthResult(outputWindow);
6569
+ if (result !== "pending") resultBox.value = result;
6570
+ }).pipe(Effect.asVoid))).pipe(Effect.asVoid);
6571
+ };
6572
+ var resolveGeminiLoginResult = (result, exitCode) => Effect.gen(function* (_) {
6573
+ if (result === "success") {
6574
+ if (exitCode !== 0) yield* _(Effect.logWarning(`Gemini CLI returned exit=${exitCode}, but authentication appears successful; continuing.`));
6575
+ return;
6576
+ }
6577
+ if (result === "failure") yield* _(Effect.fail(new AuthError({ message: "Gemini CLI OAuth authentication failed. Please try again." })));
6578
+ if (exitCode !== 0) yield* _(Effect.fail(new CommandFailedError({
6579
+ command: "gemini",
6580
+ exitCode
6581
+ })));
6582
+ });
6583
+ var printOauthInstructions = () => Effect.sync(() => {
6584
+ const port = geminiOauthCallbackPort;
6585
+ process.stderr.write("\n");
6586
+ process.stderr.write("╔═══════════════════════════════════════════════════════════════════════════╗\n");
6587
+ process.stderr.write("║ Gemini CLI OAuth Authentication ║\n");
6588
+ process.stderr.write("╠═══════════════════════════════════════════════════════════════════════════╣\n");
6589
+ process.stderr.write("║ 1. Copy the auth URL shown below and open it in your browser ║\n");
6590
+ process.stderr.write("║ 2. Sign in with your Google account ║\n");
6591
+ process.stderr.write(`║ 3. After authentication, the browser will redirect to localhost:${port} ║\n`);
6592
+ process.stderr.write("║ 4. The callback will be captured automatically (port is forwarded) ║\n");
6593
+ process.stderr.write("╚═══════════════════════════════════════════════════════════════════════════╝\n");
6594
+ process.stderr.write("\n");
6595
+ });
6596
+ var runGeminiOauthLoginWithPrompt = (cwd, accountPath, options) => Effect.scoped(Effect.gen(function* (_) {
6597
+ yield* _(printOauthInstructions());
6598
+ const proc = yield* _(startDockerProcess(yield* _(CommandExecutor.CommandExecutor), buildDockerGeminiAuthSpec(cwd, yield* _(resolveDockerVolumeHostPath(cwd, accountPath)), options.image, options.containerPath)));
6599
+ const resultBox = { value: "pending" };
6600
+ const stdoutFiber = yield* _(Effect.forkScoped(pumpDockerOutput(proc.stdout, 1, resultBox)));
6601
+ const stderrFiber = yield* _(Effect.forkScoped(pumpDockerOutput(proc.stderr, 2, resultBox)));
6602
+ const exitCode = yield* _(proc.exitCode.pipe(Effect.map(Number)));
6603
+ yield* _(Fiber$1.join(stdoutFiber));
6604
+ yield* _(Fiber$1.join(stderrFiber));
6605
+ return yield* _(resolveGeminiLoginResult(resultBox.value, exitCode));
6606
+ }));
6607
+ //#endregion
6608
+ //#region ../lib/src/usecases/auth-gemini.ts
6609
+ var geminiImageName = "docker-git-auth-gemini:latest";
6610
+ var geminiImageDir = ".docker-git/.orch/auth/gemini/.image";
6611
+ var geminiContainerHomeDir = "/gemini-home";
6612
+ var geminiCredentialsDir$1 = ".gemini";
6613
+ var geminiAuthRoot = ".docker-git/.orch/auth/gemini";
6614
+ var geminiApiKeyFileName = ".api-key";
6615
+ var geminiEnvFileName = ".env";
6616
+ var geminiApiKeyPath = (accountPath) => `${accountPath}/${geminiApiKeyFileName}`;
6617
+ var geminiEnvFilePath = (accountPath) => `${accountPath}/${geminiEnvFileName}`;
6618
+ var geminiCredentialsPath = (accountPath) => `${accountPath}/${geminiCredentialsDir$1}`;
6619
+ var renderGeminiDockerfile = () => String.raw`FROM ubuntu:24.04
6620
+ ENV DEBIAN_FRONTEND=noninteractive
6621
+ RUN apt-get update \
6622
+ && apt-get install -y --no-install-recommends ca-certificates curl bsdutils \
6623
+ && rm -rf /var/lib/apt/lists/*
6624
+ RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
6625
+ && apt-get install -y --no-install-recommends nodejs \
6626
+ && node -v \
6627
+ && npm -v \
6628
+ && rm -rf /var/lib/apt/lists/*
6629
+ RUN npm install -g @google/gemini-cli@latest
6630
+ ENTRYPOINT ["/bin/bash", "-c"]
6631
+ `;
6632
+ var ensureGeminiOrchLayout = (cwd) => migrateLegacyOrchLayout(cwd, {
6633
+ envGlobalPath: defaultTemplateConfig.envGlobalPath,
6634
+ envProjectPath: defaultTemplateConfig.envProjectPath,
6635
+ codexAuthPath: defaultTemplateConfig.codexAuthPath,
6636
+ ghAuthPath: ".docker-git/.orch/auth/gh",
6637
+ claudeAuthPath: ".docker-git/.orch/auth/claude",
6638
+ geminiAuthPath: ".docker-git/.orch/auth/gemini"
6639
+ });
6640
+ var resolveGeminiAccountPath = (path, rootPath, label) => {
6641
+ const accountLabel = normalizeAccountLabel(label, "default");
6642
+ return {
6643
+ accountLabel,
6644
+ accountPath: path.join(rootPath, accountLabel)
6645
+ };
6646
+ };
6647
+ var withGeminiAuth = (command, run, options = {}) => withFsPathContext(({ cwd, fs, path }) => Effect.gen(function* (_) {
6648
+ yield* _(ensureGeminiOrchLayout(cwd));
6649
+ const { accountLabel, accountPath } = resolveGeminiAccountPath(path, resolvePathFromCwd(path, cwd, command.geminiAuthPath), command.label);
6650
+ yield* _(fs.makeDirectory(accountPath, { recursive: true }));
6651
+ if (options.buildImage === true) yield* _(ensureDockerImage(fs, path, cwd, {
6652
+ imageName: geminiImageName,
6653
+ imageDir: geminiImageDir,
6654
+ dockerfile: renderGeminiDockerfile(),
6655
+ buildLabel: "gemini auth"
6656
+ }));
6657
+ return yield* _(run({
6658
+ accountLabel,
6659
+ accountPath,
6660
+ cwd,
6661
+ fs
6662
+ }));
6663
+ }));
6664
+ var readApiKey = (fs, accountPath) => Effect.gen(function* (_) {
6665
+ const apiKeyFilePath = geminiApiKeyPath(accountPath);
6666
+ if (yield* _(isRegularFile$1(fs, apiKeyFilePath))) {
6667
+ const trimmed = (yield* _(fs.readFileString(apiKeyFilePath), Effect.orElseSucceed(() => ""))).trim();
6668
+ if (trimmed.length > 0) return trimmed;
6669
+ }
6670
+ const envFilePath = geminiEnvFilePath(accountPath);
6671
+ if (yield* _(isRegularFile$1(fs, envFilePath))) {
6672
+ const lines = (yield* _(fs.readFileString(envFilePath), Effect.orElseSucceed(() => ""))).split("\n");
6673
+ for (const line of lines) {
6674
+ const trimmed = line.trim();
6675
+ if (trimmed.startsWith("GEMINI_API_KEY=")) {
6676
+ const value = trimmed.slice(15).replaceAll(/^['"]|['"]$/g, "").trim();
6677
+ if (value.length > 0) return value;
6678
+ }
6679
+ }
6680
+ }
6681
+ return null;
6682
+ });
6683
+ var hasOauthCredentials$1 = (fs, accountPath) => Effect.gen(function* (_) {
6684
+ const credentialsDir = geminiCredentialsPath(accountPath);
6685
+ if (!(yield* _(fs.exists(credentialsDir)))) return false;
6686
+ const possibleFiles = [
6687
+ `${credentialsDir}/oauth-tokens.json`,
6688
+ `${credentialsDir}/credentials.json`,
6689
+ `${credentialsDir}/application_default_credentials.json`
6690
+ ];
6691
+ for (const filePath of possibleFiles) if (yield* _(isRegularFile$1(fs, filePath))) return true;
6692
+ return false;
6693
+ });
6694
+ var resolveGeminiAuthMethod = (fs, accountPath) => Effect.gen(function* (_) {
6695
+ if ((yield* _(readApiKey(fs, accountPath))) !== null) return "api-key";
6696
+ return (yield* _(hasOauthCredentials$1(fs, accountPath))) ? "oauth" : "none";
6697
+ });
6698
+ var authGeminiLogin = (command, apiKey) => {
6699
+ const accountLabel = normalizeAccountLabel(command.label, "default");
6700
+ return withGeminiAuth(command, ({ accountPath, fs }) => Effect.gen(function* (_) {
6701
+ const apiKeyFilePath = geminiApiKeyPath(accountPath);
6702
+ yield* _(fs.writeFileString(apiKeyFilePath, `${apiKey.trim()}\n`));
6703
+ yield* _(fs.chmod(apiKeyFilePath, 384), Effect.orElseSucceed(() => void 0));
6704
+ })).pipe(Effect.zipRight(autoSyncState(`chore(state): auth gemini ${accountLabel}`)));
6705
+ };
6706
+ var authGeminiLoginCli = (_command) => Effect.gen(function* (_) {
6707
+ yield* _(Effect.log("Gemini CLI supports two authentication methods:"));
6708
+ yield* _(Effect.log(""));
6709
+ yield* _(Effect.log("1. API Key (recommended for simplicity):"));
6710
+ yield* _(Effect.log(" - Go to https://ai.google.dev/aistudio"));
6711
+ yield* _(Effect.log(" - Create or retrieve your API key"));
6712
+ yield* _(Effect.log(" - Use: docker-git menu -> Auth profiles -> Gemini CLI: set API key"));
6713
+ yield* _(Effect.log(""));
6714
+ yield* _(Effect.log("2. OAuth (Sign in with Google):"));
6715
+ yield* _(Effect.log(" - Use: docker-git menu -> Auth profiles -> Gemini CLI: login via OAuth"));
6716
+ yield* _(Effect.log(" - Follow the prompts to authenticate with your Google account"));
6717
+ });
6718
+ var authGeminiLoginOauth = (command) => {
6719
+ const accountLabel = normalizeAccountLabel(command.label, "default");
6720
+ return withGeminiAuth(command, ({ accountPath, cwd, fs }) => Effect.gen(function* (_) {
6721
+ const credentialsDir = geminiCredentialsPath(accountPath);
6722
+ yield* _(fs.makeDirectory(credentialsDir, { recursive: true }));
6723
+ yield* _(runGeminiOauthLoginWithPrompt(cwd, accountPath, {
6724
+ image: geminiImageName,
6725
+ containerPath: geminiContainerHomeDir
6726
+ }));
6727
+ }), { buildImage: true }).pipe(Effect.zipRight(autoSyncState(`chore(state): auth gemini oauth ${accountLabel}`)));
6728
+ };
6729
+ var authGeminiStatus = (command) => withGeminiAuth(command, ({ accountLabel, accountPath, fs }) => Effect.gen(function* (_) {
6730
+ const authMethod = yield* _(resolveGeminiAuthMethod(fs, accountPath));
6731
+ if (authMethod === "none") {
6732
+ yield* _(Effect.log(`Gemini not connected (${accountLabel}).`));
6733
+ return;
6734
+ }
6735
+ yield* _(Effect.log(`Gemini connected (${accountLabel}, ${authMethod}).`));
6736
+ }));
6737
+ var authGeminiLogout = (command) => Effect.gen(function* (_) {
6738
+ const accountLabel = normalizeAccountLabel(command.label, "default");
6739
+ yield* _(withGeminiAuth(command, ({ accountPath, fs }) => Effect.gen(function* (_) {
6740
+ yield* _(fs.remove(geminiApiKeyPath(accountPath), { force: true }));
6741
+ yield* _(fs.remove(geminiEnvFilePath(accountPath), { force: true }));
6742
+ yield* _(fs.remove(geminiCredentialsPath(accountPath), {
6743
+ recursive: true,
6744
+ force: true
6745
+ }));
6746
+ })));
6747
+ yield* _(autoSyncState(`chore(state): auth gemini logout ${accountLabel}`));
6748
+ }).pipe(Effect.asVoid);
6749
+ //#endregion
6352
6750
  //#region ../lib/src/usecases/state-repo-github.ts
6353
6751
  var dotDockerGitRepoName = ".docker-git";
6354
6752
  var defaultStateRef = "main";
@@ -7646,10 +8044,12 @@ var normalizeLabel$1 = (value) => {
7646
8044
  var defaultEnvGlobalPath = ".docker-git/.orch/env/global.env";
7647
8045
  var defaultCodexAuthPath = ".docker-git/.orch/auth/codex";
7648
8046
  var defaultClaudeAuthPath = ".docker-git/.orch/auth/claude";
8047
+ var defaultGeminiAuthPath = ".docker-git/.orch/auth/gemini";
7649
8048
  var resolveAuthOptions = (raw) => ({
7650
8049
  envGlobalPath: raw.envGlobalPath ?? defaultEnvGlobalPath,
7651
8050
  codexAuthPath: raw.codexAuthPath ?? defaultCodexAuthPath,
7652
8051
  claudeAuthPath: defaultClaudeAuthPath,
8052
+ geminiAuthPath: defaultGeminiAuthPath,
7653
8053
  label: normalizeLabel$1(raw.label),
7654
8054
  token: normalizeLabel$1(raw.token),
7655
8055
  scopes: normalizeLabel$1(raw.scopes),
@@ -7695,7 +8095,20 @@ var buildClaudeCommand = (action, options) => Match.value(action).pipe(Match.whe
7695
8095
  label: options.label,
7696
8096
  claudeAuthPath: options.claudeAuthPath
7697
8097
  })), Match.orElse(() => Either.left(invalidArgument("auth action", `unknown action '${action}'`))));
7698
- 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.orElse(() => Either.left(invalidArgument("auth provider", `unknown provider '${provider}'`))));
8098
+ var buildGeminiCommand = (action, options) => Match.value(action).pipe(Match.when("login", () => Either.right({
8099
+ _tag: "AuthGeminiLogin",
8100
+ label: options.label,
8101
+ geminiAuthPath: options.geminiAuthPath
8102
+ })), Match.when("status", () => Either.right({
8103
+ _tag: "AuthGeminiStatus",
8104
+ label: options.label,
8105
+ geminiAuthPath: options.geminiAuthPath
8106
+ })), Match.when("logout", () => Either.right({
8107
+ _tag: "AuthGeminiLogout",
8108
+ label: options.label,
8109
+ geminiAuthPath: options.geminiAuthPath
8110
+ })), Match.orElse(() => Either.left(invalidArgument("auth action", `unknown action '${action}'`))));
8111
+ 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
8112
  var parseAuth = (args) => {
7700
8113
  if (args.length < 2) return Either.left(missingArgument(args.length === 0 ? "auth provider" : "auth action"));
7701
8114
  const provider = args[0] ?? "";
@@ -7802,13 +8215,15 @@ var buildDefaultPathConfig = (normalizedSecretsRoot) => normalizedSecretsRoot ==
7802
8215
  authorizedKeysPath: defaultTemplateConfig.authorizedKeysPath,
7803
8216
  envGlobalPath: defaultTemplateConfig.envGlobalPath,
7804
8217
  envProjectPath: defaultTemplateConfig.envProjectPath,
7805
- codexAuthPath: defaultTemplateConfig.codexAuthPath
8218
+ codexAuthPath: defaultTemplateConfig.codexAuthPath,
8219
+ geminiAuthPath: defaultTemplateConfig.geminiAuthPath
7806
8220
  } : {
7807
8221
  dockerGitPath: defaultTemplateConfig.dockerGitPath,
7808
8222
  authorizedKeysPath: defaultTemplateConfig.authorizedKeysPath,
7809
8223
  envGlobalPath: `${normalizedSecretsRoot}/global.env`,
7810
8224
  envProjectPath: defaultTemplateConfig.envProjectPath,
7811
- codexAuthPath: `${normalizedSecretsRoot}/codex`
8225
+ codexAuthPath: `${normalizedSecretsRoot}/codex`,
8226
+ geminiAuthPath: `${normalizedSecretsRoot}/gemini`
7812
8227
  };
7813
8228
  var resolvePaths = (raw, repoPath) => Either.gen(function* (_) {
7814
8229
  const defaults = buildDefaultPathConfig(resolveNormalizedSecretsRoot(raw.secretsRoot));
@@ -7825,6 +8240,8 @@ var resolvePaths = (raw, repoPath) => Either.gen(function* (_) {
7825
8240
  codexAuthPath,
7826
8241
  codexSharedAuthPath: codexAuthPath,
7827
8242
  codexHome: yield* _(nonEmpty("--codex-home", raw.codexHome, defaultTemplateConfig.codexHome)),
8243
+ geminiAuthPath: defaults.geminiAuthPath,
8244
+ geminiHome: defaultTemplateConfig.geminiHome,
7828
8245
  outDir: yield* _(nonEmpty("--out-dir", raw.outDir, `.docker-git/${repoPath}`))
7829
8246
  };
7830
8247
  });
@@ -7854,6 +8271,8 @@ var buildTemplateConfig = ({ agentAuto, agentMode, claudeAuthLabel, codexAuthLab
7854
8271
  codexAuthPath: paths.codexAuthPath,
7855
8272
  codexSharedAuthPath: paths.codexSharedAuthPath,
7856
8273
  codexHome: paths.codexHome,
8274
+ geminiAuthPath: paths.geminiAuthPath,
8275
+ geminiHome: paths.geminiHome,
7857
8276
  cpuLimit,
7858
8277
  ramLimit,
7859
8278
  dockerNetworkMode,
@@ -8935,6 +9354,12 @@ var countAuthAccountDirectories = (fs, path, root) => Effect.gen(function* (_) {
8935
9354
  return count;
8936
9355
  });
8937
9356
  //#endregion
9357
+ //#region src/docker-git/menu-auth-snapshot-builder.ts
9358
+ var countAuthAccountEntries = (fs, path, claudeAuthPath, geminiAuthPath) => pipe(Effect.all({
9359
+ claudeAuthEntries: countAuthAccountDirectories(fs, path, claudeAuthPath),
9360
+ geminiAuthEntries: countAuthAccountDirectories(fs, path, geminiAuthPath)
9361
+ }));
9362
+ //#endregion
8938
9363
  //#region src/docker-git/menu-labeled-env.ts
8939
9364
  var normalizeLabel = (value) => {
8940
9365
  const trimmed = value.trim();
@@ -8983,6 +9408,18 @@ var authMenuItems = [
8983
9408
  action: "ClaudeLogout",
8984
9409
  label: "Claude Code: logout (clear cache)"
8985
9410
  },
9411
+ {
9412
+ action: "GeminiOauth",
9413
+ label: "Gemini CLI: login via OAuth (Google account)"
9414
+ },
9415
+ {
9416
+ action: "GeminiApiKey",
9417
+ label: "Gemini CLI: set API key"
9418
+ },
9419
+ {
9420
+ action: "GeminiLogout",
9421
+ label: "Gemini CLI: logout (clear credentials)"
9422
+ },
8986
9423
  {
8987
9424
  action: "Refresh",
8988
9425
  label: "Refresh snapshot"
@@ -9042,34 +9479,62 @@ var flowSteps$1 = {
9042
9479
  label: "Label to logout (empty = default)",
9043
9480
  required: false,
9044
9481
  secret: false
9482
+ }],
9483
+ GeminiOauth: [{
9484
+ key: "label",
9485
+ label: "Label (empty = default)",
9486
+ required: false,
9487
+ secret: false
9488
+ }],
9489
+ GeminiApiKey: [{
9490
+ key: "label",
9491
+ label: "Label (empty = default)",
9492
+ required: false,
9493
+ secret: false
9494
+ }, {
9495
+ key: "apiKey",
9496
+ label: "Gemini API key (from ai.google.dev)",
9497
+ required: true,
9498
+ secret: true
9499
+ }],
9500
+ GeminiLogout: [{
9501
+ key: "label",
9502
+ label: "Label to logout (empty = default)",
9503
+ required: false,
9504
+ secret: false
9045
9505
  }]
9046
9506
  };
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);
9507
+ 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);
9508
+ 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
9509
  var buildGlobalEnvPath$1 = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/env/global.env`;
9050
9510
  var buildClaudeAuthPath$1 = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/auth/claude`;
9511
+ var buildGeminiAuthPath$1 = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/auth/gemini`;
9051
9512
  var loadAuthEnvText = (cwd) => Effect.gen(function* (_) {
9052
9513
  const fs = yield* _(FileSystem.FileSystem);
9053
9514
  const path = yield* _(Path.Path);
9054
9515
  const globalEnvPath = buildGlobalEnvPath$1(cwd);
9055
9516
  const claudeAuthPath = buildClaudeAuthPath$1(cwd);
9517
+ const geminiAuthPath = buildGeminiAuthPath$1(cwd);
9056
9518
  yield* _(ensureEnvFile$1(fs, path, globalEnvPath));
9057
9519
  return {
9058
9520
  fs,
9059
9521
  path,
9060
9522
  globalEnvPath,
9061
9523
  claudeAuthPath,
9524
+ geminiAuthPath,
9062
9525
  envText: yield* _(readEnvText(fs, globalEnvPath))
9063
9526
  };
9064
9527
  });
9065
- var readAuthSnapshot = (cwd) => pipe(loadAuthEnvText(cwd), Effect.flatMap(({ claudeAuthPath, envText, fs, globalEnvPath, path }) => pipe(countAuthAccountDirectories(fs, path, claudeAuthPath), Effect.map((claudeAuthEntries) => ({
9528
+ 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
9529
  globalEnvPath,
9067
9530
  claudeAuthPath,
9531
+ geminiAuthPath,
9068
9532
  totalEntries: parseEnvEntries(envText).filter((entry) => entry.value.trim().length > 0).length,
9069
9533
  githubTokenEntries: countKeyEntries(envText, "GITHUB_TOKEN"),
9070
9534
  gitTokenEntries: countKeyEntries(envText, "GIT_AUTH_TOKEN"),
9071
9535
  gitUserEntries: countKeyEntries(envText, "GIT_AUTH_USER"),
9072
- claudeAuthEntries
9536
+ claudeAuthEntries,
9537
+ geminiAuthEntries
9073
9538
  })))));
9074
9539
  var writeAuthFlow = (cwd, flow, values) => pipe(loadAuthEnvText(cwd), Effect.flatMap(({ envText, fs, globalEnvPath }) => {
9075
9540
  const label = values["label"] ?? "";
@@ -9098,6 +9563,67 @@ var authMenuActionByIndex = (index) => {
9098
9563
  };
9099
9564
  var authMenuSize = () => authMenuItems.length;
9100
9565
  //#endregion
9566
+ //#region src/docker-git/menu-auth-effects.ts
9567
+ var resolveLabelOption = (values) => {
9568
+ const labelValue = (values["label"] ?? "").trim();
9569
+ return labelValue.length > 0 ? labelValue : null;
9570
+ };
9571
+ var resolveGithubOauthEffect = (labelOption, globalEnvPath) => authGithubLogin({
9572
+ _tag: "AuthGithubLogin",
9573
+ label: labelOption,
9574
+ token: null,
9575
+ scopes: null,
9576
+ envGlobalPath: globalEnvPath
9577
+ });
9578
+ var resolveClaudeOauthEffect = (labelOption) => authClaudeLogin({
9579
+ _tag: "AuthClaudeLogin",
9580
+ label: labelOption,
9581
+ claudeAuthPath: claudeAuthRoot
9582
+ });
9583
+ var resolveClaudeLogoutEffect = (labelOption) => authClaudeLogout({
9584
+ _tag: "AuthClaudeLogout",
9585
+ label: labelOption,
9586
+ claudeAuthPath: claudeAuthRoot
9587
+ });
9588
+ var resolveGeminiOauthEffect = (labelOption) => authGeminiLoginOauth({
9589
+ _tag: "AuthGeminiLogin",
9590
+ label: labelOption,
9591
+ geminiAuthPath: geminiAuthRoot
9592
+ });
9593
+ var resolveGeminiApiKeyEffect = (labelOption, apiKey) => authGeminiLogin({
9594
+ _tag: "AuthGeminiLogin",
9595
+ label: labelOption,
9596
+ geminiAuthPath: geminiAuthRoot
9597
+ }, apiKey);
9598
+ var resolveGeminiLogoutEffect = (labelOption) => authGeminiLogout({
9599
+ _tag: "AuthGeminiLogout",
9600
+ label: labelOption,
9601
+ geminiAuthPath: geminiAuthRoot
9602
+ });
9603
+ var resolveAuthPromptEffect = (view, cwd, values) => {
9604
+ const labelOption = resolveLabelOption(values);
9605
+ 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);
9606
+ };
9607
+ var startAuthMenuWithSnapshot = (snapshot, context) => {
9608
+ context.setView({
9609
+ _tag: "AuthMenu",
9610
+ selected: 0,
9611
+ snapshot
9612
+ });
9613
+ context.setMessage(null);
9614
+ };
9615
+ var runAuthPromptEffect = (effect, view, label, context, options) => {
9616
+ const withOptionalSuspension = options.suspendTui ? withSuspendedTui(effect, {
9617
+ onError: pauseOnError(renderError),
9618
+ onResume: resumeSshWithSkipInputs(context)
9619
+ }) : effect;
9620
+ context.setSshActive(options.suspendTui);
9621
+ context.runner.runEffect(pipe(withOptionalSuspension, Effect.zipRight(readAuthSnapshot(context.cwd)), Effect.tap((snapshot) => Effect.sync(() => {
9622
+ startAuthMenuWithSnapshot(snapshot, context);
9623
+ context.setMessage(successMessage$1(view.flow, label));
9624
+ })), Effect.asVoid));
9625
+ };
9626
+ //#endregion
9101
9627
  //#region src/docker-git/menu-input-utils.ts
9102
9628
  var parseMenuIndex = (input) => {
9103
9629
  const trimmed = input.trim();
@@ -9154,14 +9680,6 @@ var defaultLabel = (value) => {
9154
9680
  const trimmed = value.trim();
9155
9681
  return trimmed.length > 0 ? trimmed : "default";
9156
9682
  };
9157
- var startAuthMenuWithSnapshot = (snapshot, context) => {
9158
- context.setView({
9159
- _tag: "AuthMenu",
9160
- selected: 0,
9161
- snapshot
9162
- });
9163
- context.setMessage(null);
9164
- };
9165
9683
  var startAuthPrompt = (snapshot, flow, context) => {
9166
9684
  context.setView({
9167
9685
  _tag: "AuthPrompt",
@@ -9173,39 +9691,6 @@ var startAuthPrompt = (snapshot, flow, context) => {
9173
9691
  });
9174
9692
  context.setMessage(null);
9175
9693
  };
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
9694
  var loadAuthMenuView = (cwd, context) => pipe(readAuthSnapshot(cwd), Effect.tap((snapshot) => Effect.sync(() => {
9210
9695
  startAuthMenuWithSnapshot(snapshot, context);
9211
9696
  })), Effect.asVoid);
@@ -9225,7 +9710,10 @@ var submitAuthPrompt = (view, context) => {
9225
9710
  startAuthMenuWithSnapshot(view.snapshot, context);
9226
9711
  }, (nextValues) => {
9227
9712
  const label = defaultLabel(nextValues["label"] ?? "");
9228
- runAuthPromptEffect(resolveAuthPromptEffect(view, context.state.cwd, nextValues), view, label, context, { suspendTui: view.flow === "GithubOauth" || view.flow === "ClaudeOauth" || view.flow === "ClaudeLogout" });
9713
+ runAuthPromptEffect(resolveAuthPromptEffect(view, context.state.cwd, nextValues), view, label, {
9714
+ ...context,
9715
+ cwd: context.state.cwd
9716
+ }, { suspendTui: view.flow === "GithubOauth" || view.flow === "ClaudeOauth" || view.flow === "ClaudeLogout" || view.flow === "GeminiOauth" });
9229
9717
  });
9230
9718
  };
9231
9719
  var setAuthMenuSelection = (view, selected, context) => {
@@ -9267,6 +9755,15 @@ var handleAuthMenuInput = (input, key, view, context) => {
9267
9755
  }
9268
9756
  handleAuthMenuNumberInput(input, view, context);
9269
9757
  };
9758
+ var setAuthPromptBuffer = (args) => {
9759
+ const { context, input, key, view } = args;
9760
+ const nextBuffer = nextBufferValue(input, key, view.buffer);
9761
+ if (nextBuffer === null) return;
9762
+ context.setView({
9763
+ ...view,
9764
+ buffer: nextBuffer
9765
+ });
9766
+ };
9270
9767
  var handleAuthPromptInput = (input, key, view, context) => {
9271
9768
  if (key.escape) {
9272
9769
  startAuthMenuWithSnapshot(view.snapshot, context);
@@ -9283,15 +9780,6 @@ var handleAuthPromptInput = (input, key, view, context) => {
9283
9780
  context
9284
9781
  });
9285
9782
  };
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
9783
  var openAuthMenu = (context) => {
9296
9784
  context.setMessage("Loading auth profiles...");
9297
9785
  context.runner.runEffect(loadAuthMenuView(context.state.cwd, context));
@@ -9384,15 +9872,17 @@ var loadRuntimeByProject = (items) => pipe(runDockerPsNames(process.cwd()), Effe
9384
9872
  }));
9385
9873
  var runtimeForSelection = (view, selected) => view.runtimeByProject[selected.projectDir] ?? stoppedRuntime$1();
9386
9874
  //#endregion
9875
+ //#region src/docker-git/menu-project-auth-helpers.ts
9876
+ var hasFileAtPath = (fs, filePath) => Effect.gen(function* (_) {
9877
+ if (!(yield* _(fs.exists(filePath)))) return false;
9878
+ return (yield* _(fs.stat(filePath))).type === "File";
9879
+ });
9880
+ //#endregion
9387
9881
  //#region src/docker-git/menu-project-auth-claude.ts
9388
9882
  var oauthTokenFileName = ".oauth-token";
9389
9883
  var legacyConfigFileName = ".config.json";
9390
9884
  var credentialsFileName = ".credentials.json";
9391
9885
  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
9886
  var hasNonEmptyOauthToken = (fs, tokenPath) => Effect.gen(function* (_) {
9397
9887
  if (!(yield* _(hasFileAtPath(fs, tokenPath)))) return false;
9398
9888
  return (yield* _(fs.readFileString(tokenPath), Effect.orElseSucceed(() => ""))).trim().length > 0;
@@ -9419,6 +9909,107 @@ var hasClaudeAccountCredentials = (fs, accountPath) => hasFileAtPath(fs, `${acco
9419
9909
  }));
9420
9910
  }));
9421
9911
  //#endregion
9912
+ //#region src/docker-git/menu-project-auth-gemini.ts
9913
+ var apiKeyFileName = ".api-key";
9914
+ var envFileName = ".env";
9915
+ var geminiCredentialsDir = ".gemini";
9916
+ var hasNonEmptyApiKey = (fs, apiKeyPath) => Effect.gen(function* (_) {
9917
+ if (!(yield* _(hasFileAtPath(fs, apiKeyPath)))) return false;
9918
+ return (yield* _(fs.readFileString(apiKeyPath), Effect.orElseSucceed(() => ""))).trim().length > 0;
9919
+ });
9920
+ var hasApiKeyInEnvFile = (fs, envFilePath) => Effect.gen(function* (_) {
9921
+ if (!(yield* _(hasFileAtPath(fs, envFilePath)))) return false;
9922
+ const lines = (yield* _(fs.readFileString(envFilePath), Effect.orElseSucceed(() => ""))).split("\n");
9923
+ for (const line of lines) {
9924
+ const trimmed = line.trim();
9925
+ if (trimmed.startsWith("GEMINI_API_KEY=")) {
9926
+ if (trimmed.slice(15).replaceAll(/^['"]|['"]$/g, "").trim().length > 0) return true;
9927
+ }
9928
+ }
9929
+ return false;
9930
+ });
9931
+ var geminiOauthCredentialFiles = [
9932
+ "oauth-tokens.json",
9933
+ "credentials.json",
9934
+ "application_default_credentials.json"
9935
+ ];
9936
+ var checkAnyFileExists = (fs, basePath, fileNames) => {
9937
+ const [first, ...rest] = fileNames;
9938
+ if (first === void 0) return Effect.succeed(false);
9939
+ return hasFileAtPath(fs, `${basePath}/${first}`).pipe(Effect.flatMap((exists) => exists ? Effect.succeed(true) : checkAnyFileExists(fs, basePath, rest)));
9940
+ };
9941
+ var hasOauthCredentials = (fs, accountPath) => {
9942
+ const credentialsDir = `${accountPath}/${geminiCredentialsDir}`;
9943
+ return hasFileAtPath(fs, credentialsDir).pipe(Effect.flatMap((dirExists) => dirExists ? checkAnyFileExists(fs, credentialsDir, geminiOauthCredentialFiles) : Effect.succeed(false)));
9944
+ };
9945
+ var hasGeminiAccountCredentials = (fs, accountPath) => hasNonEmptyApiKey(fs, `${accountPath}/${apiKeyFileName}`).pipe(Effect.flatMap((hasApiKey) => {
9946
+ if (hasApiKey) return Effect.succeed(true);
9947
+ return hasApiKeyInEnvFile(fs, `${accountPath}/${envFileName}`).pipe(Effect.flatMap((hasEnvApiKey) => {
9948
+ if (hasEnvApiKey) return Effect.succeed(true);
9949
+ return hasOauthCredentials(fs, accountPath);
9950
+ }));
9951
+ }));
9952
+ //#endregion
9953
+ //#region src/docker-git/menu-project-auth-flows.ts
9954
+ var githubTokenBaseKey$1 = "GITHUB_TOKEN";
9955
+ var gitTokenBaseKey$1 = "GIT_AUTH_TOKEN";
9956
+ var gitUserBaseKey = "GIT_AUTH_USER";
9957
+ var projectGithubLabelKey$1 = "GITHUB_AUTH_LABEL";
9958
+ var projectGitLabelKey$1 = "GIT_AUTH_LABEL";
9959
+ var projectClaudeLabelKey$1 = "CLAUDE_AUTH_LABEL";
9960
+ var projectGeminiLabelKey$1 = "GEMINI_AUTH_LABEL";
9961
+ var defaultGitUser = "x-access-token";
9962
+ var missingSecret = (provider, label, envPath) => new AuthError({ message: `${provider} not connected: label '${label}' not found in ${envPath}` });
9963
+ var clearProjectGitLabels = (envText) => {
9964
+ return upsertEnvKey(upsertEnvKey(upsertEnvKey(envText, "GH_TOKEN", ""), projectGitLabelKey$1, ""), projectGithubLabelKey$1, "");
9965
+ };
9966
+ var updateProjectGithubConnect = (spec) => {
9967
+ const key = buildLabeledEnvKey(githubTokenBaseKey$1, spec.rawLabel);
9968
+ const token = findEnvValue(spec.globalEnvText, key);
9969
+ if (token === null) return Effect.fail(missingSecret("GitHub token", spec.canonicalLabel, spec.globalEnvPath));
9970
+ const withoutGitLabel = upsertEnvKey(upsertEnvKey(upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", token), "GH_TOKEN", token), projectGitLabelKey$1, "");
9971
+ return Effect.succeed(upsertEnvKey(withoutGitLabel, projectGithubLabelKey$1, spec.canonicalLabel));
9972
+ };
9973
+ var updateProjectGithubDisconnect = (spec) => {
9974
+ const withoutGitToken = upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", "");
9975
+ return Effect.succeed(clearProjectGitLabels(withoutGitToken));
9976
+ };
9977
+ var updateProjectGitConnect = (spec) => {
9978
+ const tokenKey = buildLabeledEnvKey(gitTokenBaseKey$1, spec.rawLabel);
9979
+ const userKey = buildLabeledEnvKey(gitUserBaseKey, spec.rawLabel);
9980
+ const token = findEnvValue(spec.globalEnvText, tokenKey);
9981
+ if (token === null) return Effect.fail(missingSecret("Git credentials", spec.canonicalLabel, spec.globalEnvPath));
9982
+ const defaultUser = findEnvValue(spec.globalEnvText, gitUserBaseKey) ?? defaultGitUser;
9983
+ const user = findEnvValue(spec.globalEnvText, userKey) ?? defaultUser;
9984
+ const withGitLabel = upsertEnvKey(upsertEnvKey(upsertEnvKey(upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", token), "GIT_AUTH_USER", user), "GH_TOKEN", token), projectGitLabelKey$1, spec.canonicalLabel);
9985
+ return Effect.succeed(upsertEnvKey(withGitLabel, projectGithubLabelKey$1, spec.canonicalLabel));
9986
+ };
9987
+ var updateProjectGitDisconnect = (spec) => {
9988
+ const withoutUser = upsertEnvKey(upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", ""), "GIT_AUTH_USER", "");
9989
+ return Effect.succeed(clearProjectGitLabels(withoutUser));
9990
+ };
9991
+ var resolveAccountCandidates = (authPath, accountLabel) => accountLabel === "default" ? [`${authPath}/default`, authPath] : [`${authPath}/${accountLabel}`];
9992
+ var findFirstCredentialsMatch = (fs, candidates, hasCredentials) => Effect.gen(function* (_) {
9993
+ for (const accountPath of candidates) {
9994
+ if (!(yield* _(fs.exists(accountPath)))) continue;
9995
+ if (yield* _(hasCredentials(fs, accountPath), Effect.orElseSucceed(() => false))) return accountPath;
9996
+ }
9997
+ return null;
9998
+ });
9999
+ var updateProjectClaudeConnect = (spec) => {
10000
+ const accountLabel = normalizeAccountLabel(spec.rawLabel, "default");
10001
+ const accountCandidates = resolveAccountCandidates(spec.claudeAuthPath, accountLabel);
10002
+ 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))));
10003
+ };
10004
+ var updateProjectClaudeDisconnect = (spec) => Effect.succeed(upsertEnvKey(spec.projectEnvText, projectClaudeLabelKey$1, ""));
10005
+ var updateProjectGeminiConnect = (spec) => {
10006
+ const accountLabel = normalizeAccountLabel(spec.rawLabel, "default");
10007
+ const accountCandidates = resolveAccountCandidates(spec.geminiAuthPath, accountLabel);
10008
+ 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))));
10009
+ };
10010
+ var updateProjectGeminiDisconnect = (spec) => Effect.succeed(upsertEnvKey(spec.projectEnvText, projectGeminiLabelKey$1, ""));
10011
+ 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);
10012
+ //#endregion
9422
10013
  //#region src/docker-git/menu-project-auth-data.ts
9423
10014
  var projectAuthMenuItems = [
9424
10015
  {
@@ -9445,6 +10036,14 @@ var projectAuthMenuItems = [
9445
10036
  action: "ProjectClaudeDisconnect",
9446
10037
  label: "Project: Claude disconnect"
9447
10038
  },
10039
+ {
10040
+ action: "ProjectGeminiConnect",
10041
+ label: "Project: Gemini connect label"
10042
+ },
10043
+ {
10044
+ action: "ProjectGeminiDisconnect",
10045
+ label: "Project: Gemini disconnect"
10046
+ },
9448
10047
  {
9449
10048
  action: "Refresh",
9450
10049
  label: "Refresh snapshot"
@@ -9475,7 +10074,14 @@ var flowSteps = {
9475
10074
  required: false,
9476
10075
  secret: false
9477
10076
  }],
9478
- ProjectClaudeDisconnect: []
10077
+ ProjectClaudeDisconnect: [],
10078
+ ProjectGeminiConnect: [{
10079
+ key: "label",
10080
+ label: "Label (empty = default)",
10081
+ required: false,
10082
+ secret: false
10083
+ }],
10084
+ ProjectGeminiDisconnect: []
9479
10085
  };
9480
10086
  var resolveCanonicalLabel = (value) => {
9481
10087
  const normalized = normalizeLabel(value);
@@ -9483,18 +10089,19 @@ var resolveCanonicalLabel = (value) => {
9483
10089
  };
9484
10090
  var githubTokenBaseKey = "GITHUB_TOKEN";
9485
10091
  var gitTokenBaseKey = "GIT_AUTH_TOKEN";
9486
- var gitUserBaseKey = "GIT_AUTH_USER";
9487
10092
  var projectGithubLabelKey = "GITHUB_AUTH_LABEL";
9488
10093
  var projectGitLabelKey = "GIT_AUTH_LABEL";
9489
10094
  var projectClaudeLabelKey = "CLAUDE_AUTH_LABEL";
9490
- var defaultGitUser = "x-access-token";
10095
+ var projectGeminiLabelKey = "GEMINI_AUTH_LABEL";
9491
10096
  var buildGlobalEnvPath = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/env/global.env`;
9492
10097
  var buildClaudeAuthPath = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/auth/claude`;
10098
+ var buildGeminiAuthPath = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/auth/gemini`;
9493
10099
  var loadProjectAuthEnvText = (project) => Effect.gen(function* (_) {
9494
10100
  const fs = yield* _(FileSystem.FileSystem);
9495
10101
  const path = yield* _(Path.Path);
9496
10102
  const globalEnvPath = buildGlobalEnvPath(process.cwd());
9497
10103
  const claudeAuthPath = buildClaudeAuthPath(process.cwd());
10104
+ const geminiAuthPath = buildGeminiAuthPath(process.cwd());
9498
10105
  yield* _(ensureEnvFile$1(fs, path, globalEnvPath));
9499
10106
  yield* _(ensureEnvFile$1(fs, path, project.envProjectPath));
9500
10107
  const globalEnvText = yield* _(readEnvText(fs, globalEnvPath));
@@ -9505,69 +10112,29 @@ var loadProjectAuthEnvText = (project) => Effect.gen(function* (_) {
9505
10112
  globalEnvPath,
9506
10113
  projectEnvPath: project.envProjectPath,
9507
10114
  claudeAuthPath,
10115
+ geminiAuthPath,
9508
10116
  globalEnvText,
9509
10117
  projectEnvText
9510
10118
  };
9511
10119
  });
9512
- var readProjectAuthSnapshot = (project) => pipe(loadProjectAuthEnvText(project), Effect.flatMap(({ claudeAuthPath, fs, globalEnvPath, globalEnvText, path, projectEnvPath, projectEnvText }) => pipe(countAuthAccountDirectories(fs, path, claudeAuthPath), Effect.map((claudeAuthEntries) => ({
10120
+ 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
10121
  projectDir: project.projectDir,
9514
10122
  projectName: project.displayName,
9515
10123
  envGlobalPath: globalEnvPath,
9516
10124
  envProjectPath: projectEnvPath,
9517
10125
  claudeAuthPath,
10126
+ geminiAuthPath,
9518
10127
  githubTokenEntries: countKeyEntries(globalEnvText, githubTokenBaseKey),
9519
10128
  gitTokenEntries: countKeyEntries(globalEnvText, gitTokenBaseKey),
9520
10129
  claudeAuthEntries,
10130
+ geminiAuthEntries,
9521
10131
  activeGithubLabel: findEnvValue(projectEnvText, projectGithubLabelKey),
9522
10132
  activeGitLabel: findEnvValue(projectEnvText, projectGitLabelKey),
9523
- activeClaudeLabel: findEnvValue(projectEnvText, projectClaudeLabelKey)
10133
+ activeClaudeLabel: findEnvValue(projectEnvText, projectClaudeLabelKey),
10134
+ activeGeminiLabel: findEnvValue(projectEnvText, projectGeminiLabelKey)
9524
10135
  })))));
9525
- var missingSecret = (provider, label, envPath) => new AuthError({ message: `${provider} not connected: label '${label}' not found in ${envPath}` });
9526
- var updateProjectGithubConnect = (spec) => {
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 }) => {
10136
+ 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);
10137
+ var writeProjectAuthFlow = (project, flow, values) => pipe(loadProjectAuthEnvText(project), Effect.flatMap(({ claudeAuthPath, fs, geminiAuthPath, globalEnvPath, globalEnvText, projectEnvPath, projectEnvText }) => {
9571
10138
  const rawLabel = values["label"] ?? "";
9572
10139
  const canonicalLabel = resolveCanonicalLabel(rawLabel);
9573
10140
  const nextProjectEnv = resolveProjectEnvUpdate(flow, {
@@ -9577,9 +10144,10 @@ var writeProjectAuthFlow = (project, flow, values) => pipe(loadProjectAuthEnvTex
9577
10144
  globalEnvPath,
9578
10145
  globalEnvText,
9579
10146
  projectEnvText,
9580
- claudeAuthPath
10147
+ claudeAuthPath,
10148
+ geminiAuthPath
9581
10149
  });
9582
- const syncMessage = Match.value(flow).pipe(Match.when("ProjectGithubConnect", () => `chore(state): project auth gh ${canonicalLabel} ${project.displayName}`), Match.when("ProjectGithubDisconnect", () => `chore(state): project auth gh logout ${project.displayName}`), Match.when("ProjectGitConnect", () => `chore(state): project auth git ${canonicalLabel} ${project.displayName}`), Match.when("ProjectGitDisconnect", () => `chore(state): project auth git logout ${project.displayName}`), Match.when("ProjectClaudeConnect", () => `chore(state): project auth claude ${canonicalLabel} ${project.displayName}`), Match.when("ProjectClaudeDisconnect", () => `chore(state): project auth claude logout ${project.displayName}`), Match.exhaustive);
10150
+ const syncMessage = resolveSyncMessage(flow, canonicalLabel, project.displayName);
9583
10151
  return pipe(nextProjectEnv, Effect.flatMap((nextText) => fs.writeFileString(projectEnvPath, nextText)), Effect.zipRight(autoSyncState(syncMessage)));
9584
10152
  }), Effect.asVoid);
9585
10153
  var projectAuthViewSteps = (flow) => flowSteps[flow];
@@ -9615,7 +10183,7 @@ var startProjectAuthPrompt = (project, snapshot, flow, context) => {
9615
10183
  var loadProjectAuthMenuView = (project, context) => pipe(readProjectAuthSnapshot(project), Effect.tap((snapshot) => Effect.sync(() => {
9616
10184
  startProjectAuthMenu(project, snapshot, context);
9617
10185
  })), 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);
10186
+ 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
10187
  var runProjectAuthEffect = (project, flow, values, label, context) => {
9620
10188
  context.runner.runEffect(pipe(writeProjectAuthFlow(project, flow, values), Effect.zipRight(readProjectAuthSnapshot(project)), Effect.tap((snapshot) => Effect.sync(() => {
9621
10189
  startProjectAuthMenu(project, snapshot, context);
@@ -9640,7 +10208,7 @@ var runProjectAuthAction$1 = (action, view, context) => {
9640
10208
  context.runner.runEffect(loadProjectAuthMenuView(view.project, context));
9641
10209
  return;
9642
10210
  }
9643
- if (action === "ProjectGithubDisconnect" || action === "ProjectGitDisconnect" || action === "ProjectClaudeDisconnect") {
10211
+ if (action === "ProjectGithubDisconnect" || action === "ProjectGitDisconnect" || action === "ProjectClaudeDisconnect" || action === "ProjectGeminiDisconnect") {
9644
10212
  runProjectAuthEffect(view.project, action, {}, "default", context);
9645
10213
  return;
9646
10214
  }
@@ -10810,7 +11378,7 @@ var setExitCode = (code) => Effect.sync(() => {
10810
11378
  });
10811
11379
  var logWarningAndExit = (error) => pipe(Effect.logWarning(renderError(error)), Effect.tap(() => setExitCode(1)), Effect.asVoid);
10812
11380
  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: "SessionsKill" }, (cmd) => killTerminalProcess(cmd))).pipe(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);
11381
+ var handleNonBaseCommand = (command) => Match.value(command).pipe(Match.when({ _tag: "StatePath" }, () => statePath), Match.when({ _tag: "StateInit" }, (cmd) => stateInit(cmd)), Match.when({ _tag: "StateStatus" }, () => stateStatus), Match.when({ _tag: "StatePull" }, () => statePull), Match.when({ _tag: "StateCommit" }, (cmd) => stateCommit(cmd.message)), Match.when({ _tag: "StatePush" }, () => statePush), Match.when({ _tag: "StateSync" }, (cmd) => stateSync(cmd.message)), Match.when({ _tag: "AuthGithubLogin" }, (cmd) => authGithubLogin(cmd)), Match.when({ _tag: "AuthGithubStatus" }, (cmd) => authGithubStatus(cmd)), Match.when({ _tag: "AuthGithubLogout" }, (cmd) => authGithubLogout(cmd)), Match.when({ _tag: "AuthCodexLogin" }, (cmd) => authCodexLogin(cmd)), Match.when({ _tag: "AuthCodexStatus" }, (cmd) => authCodexStatus(cmd)), Match.when({ _tag: "AuthCodexLogout" }, (cmd) => authCodexLogout(cmd)), Match.when({ _tag: "AuthClaudeLogin" }, (cmd) => authClaudeLogin(cmd)), Match.when({ _tag: "AuthClaudeStatus" }, (cmd) => authClaudeStatus(cmd)), Match.when({ _tag: "AuthClaudeLogout" }, (cmd) => authClaudeLogout(cmd)), Match.when({ _tag: "Attach" }, (cmd) => attachTmux(cmd)), Match.when({ _tag: "Panes" }, (cmd) => listTmuxPanes(cmd)), Match.when({ _tag: "SessionsList" }, (cmd) => listTerminalSessions(cmd))).pipe(Match.when({ _tag: "AuthGeminiLogin" }, (cmd) => authGeminiLoginCli(cmd)), Match.when({ _tag: "AuthGeminiStatus" }, (cmd) => authGeminiStatus(cmd)), Match.when({ _tag: "AuthGeminiLogout" }, (cmd) => authGeminiLogout(cmd)), Match.when({ _tag: "SessionsKill" }, (cmd) => killTerminalProcess(cmd)), Match.when({ _tag: "Apply" }, (cmd) => applyProjectConfig(cmd)), Match.when({ _tag: "SessionsLogs" }, (cmd) => tailTerminalLogs(cmd)), Match.when({ _tag: "ScrapExport" }, (cmd) => exportScrap(cmd)), Match.when({ _tag: "ScrapImport" }, (cmd) => importScrap(cmd)), Match.when({ _tag: "McpPlaywrightUp" }, (cmd) => mcpPlaywrightUp(cmd)), Match.exhaustive);
10814
11382
  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
11383
  onFailure: (error) => isParseError(error) ? logErrorAndExit(error) : pipe(Effect.logError(renderError(error)), Effect.flatMap(() => Effect.fail(error))),
10816
11384
  onSuccess: () => Effect.void