@prover-coder-ai/docker-git 1.0.42 → 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
  });
@@ -1211,6 +1217,38 @@ var ensureGhAuthImage = (fs, path, cwd, buildLabel) => ensureDockerImage(fs, pat
1211
1217
  buildLabel
1212
1218
  });
1213
1219
  //#endregion
1220
+ //#region ../lib/src/usecases/github-api-helpers.ts
1221
+ /**
1222
+ * Run `gh api <args>` inside the auth Docker container and return trimmed stdout.
1223
+ *
1224
+ * @pure false
1225
+ * @effect CommandExecutor (Docker)
1226
+ * @invariant exits with CommandFailedError on non-zero exit code
1227
+ * @complexity O(1)
1228
+ */
1229
+ var runGhApiCapture = (cwd, hostPath, token, args) => runDockerAuthCapture(buildDockerAuthSpec({
1230
+ cwd,
1231
+ image: ghImageName,
1232
+ hostPath,
1233
+ containerPath: ghAuthDir,
1234
+ env: `GH_TOKEN=${token}`,
1235
+ args: ["api", ...args],
1236
+ interactive: false
1237
+ }), [0], (exitCode) => new CommandFailedError({
1238
+ command: `gh api ${args.join(" ")}`,
1239
+ exitCode
1240
+ })).pipe(Effect.map((raw) => raw.trim()));
1241
+ /**
1242
+ * Like `runGhApiCapture` but returns `null` instead of failing on API errors
1243
+ * (e.g. HTTP 404 / non-zero exit code).
1244
+ *
1245
+ * @pure false
1246
+ * @effect CommandExecutor (Docker)
1247
+ * @invariant never fails — errors become null
1248
+ * @complexity O(1)
1249
+ */
1250
+ var runGhApiNullable = (cwd, hostPath, token, args) => runGhApiCapture(cwd, hostPath, token, args).pipe(Effect.catchTag("CommandFailedError", () => Effect.succeed("")), Effect.map((raw) => raw.length === 0 ? null : raw));
1251
+ //#endregion
1214
1252
  //#region ../lib/src/usecases/runtime.ts
1215
1253
  var withFsPathContext = (run) => Effect.gen(function* (_) {
1216
1254
  return yield* _(run({
@@ -1228,20 +1266,7 @@ var resolveGithubToken$1 = (envText) => {
1228
1266
  const labeled = entries.find((entry) => entry.key.startsWith("GITHUB_TOKEN__"));
1229
1267
  return labeled && labeled.value.trim().length > 0 ? labeled.value.trim() : null;
1230
1268
  };
1231
- var runGhApiCapture = (cwd, hostPath, token, args) => runDockerAuthCapture(buildDockerAuthSpec({
1232
- cwd,
1233
- image: ghImageName,
1234
- hostPath,
1235
- containerPath: ghAuthDir,
1236
- env: `GH_TOKEN=${token}`,
1237
- args: ["api", ...args],
1238
- interactive: false
1239
- }), [0], (exitCode) => new CommandFailedError({
1240
- command: `gh api ${args.join(" ")}`,
1241
- exitCode
1242
- })).pipe(Effect.map((raw) => raw.trim()));
1243
- var runGhApiCloneUrl = (cwd, hostPath, token, args) => runGhApiCapture(cwd, hostPath, token, args).pipe(Effect.catchTag("CommandFailedError", () => Effect.succeed("")), Effect.map((raw) => raw.length === 0 ? null : raw));
1244
- var resolveViewerLogin = (cwd, hostPath, token) => Effect.gen(function* (_) {
1269
+ var resolveViewerLogin$1 = (cwd, hostPath, token) => Effect.gen(function* (_) {
1245
1270
  const command = "gh api /user --jq .login";
1246
1271
  const raw = yield* _(runGhApiCapture(cwd, hostPath, token, [
1247
1272
  "/user",
@@ -1254,12 +1279,12 @@ var resolveViewerLogin = (cwd, hostPath, token) => Effect.gen(function* (_) {
1254
1279
  })));
1255
1280
  return raw;
1256
1281
  });
1257
- var resolveRepoCloneUrl = (cwd, hostPath, token, fullName) => runGhApiCloneUrl(cwd, hostPath, token, [
1282
+ var resolveRepoCloneUrl = (cwd, hostPath, token, fullName) => runGhApiNullable(cwd, hostPath, token, [
1258
1283
  `/repos/${fullName}`,
1259
1284
  "--jq",
1260
1285
  ".clone_url"
1261
1286
  ]);
1262
- var createFork = (cwd, hostPath, token, owner, repo) => runGhApiCloneUrl(cwd, hostPath, token, [
1287
+ var createFork = (cwd, hostPath, token, owner, repo) => runGhApiNullable(cwd, hostPath, token, [
1263
1288
  "-X",
1264
1289
  "POST",
1265
1290
  `/repos/${owner}/${repo}/forks`,
@@ -1277,7 +1302,7 @@ var resolveGithubForkUrl = (repoUrl, envGlobalPath) => withFsPathContext(({ cwd,
1277
1302
  const ghRoot = resolvePathFromCwd(path, cwd, ghAuthRoot);
1278
1303
  yield* _(fs.makeDirectory(ghRoot, { recursive: true }));
1279
1304
  yield* _(ensureGhAuthImage(fs, path, cwd, "gh api"));
1280
- const viewer = yield* _(resolveViewerLogin(cwd, ghRoot, token));
1305
+ const viewer = yield* _(resolveViewerLogin$1(cwd, ghRoot, token));
1281
1306
  if (viewer.toLowerCase() === repo.owner.toLowerCase()) return null;
1282
1307
  const existingFork = yield* _(resolveRepoCloneUrl(cwd, ghRoot, token, `${viewer}/${repo.repo}`));
1283
1308
  if (existingFork !== null) return existingFork;
@@ -1338,6 +1363,9 @@ var TemplateConfigSchema = Schema.Struct({
1338
1363
  codexAuthPath: Schema.String,
1339
1364
  codexSharedAuthPath: Schema.optionalWith(Schema.String, { default: () => defaultTemplateConfig.codexSharedAuthPath }),
1340
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 }),
1341
1369
  cpuLimit: Schema.optionalWith(Schema.String, { default: () => defaultTemplateConfig.cpuLimit }),
1342
1370
  ramLimit: Schema.optionalWith(Schema.String, { default: () => defaultTemplateConfig.ramLimit }),
1343
1371
  dockerNetworkMode: Schema.optionalWith(Schema.Literal("shared", "project"), { default: () => defaultTemplateConfig.dockerNetworkMode }),
@@ -1588,28 +1616,15 @@ var resolveComposeResourceLimits = (template, hostResources) => {
1588
1616
  //#region ../lib/src/usecases/resource-limits.ts
1589
1617
  var resolveTemplateResourceLimits = (template) => Effect.succeed(withDefaultResourceLimitIntent(template));
1590
1618
  //#endregion
1591
- //#region ../lib/src/usecases/state-repo/env.ts
1592
- var isTruthyEnv = (value) => {
1593
- const normalized = value.trim().toLowerCase();
1594
- return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
1595
- };
1596
- var isFalsyEnv = (value) => {
1597
- const normalized = value.trim().toLowerCase();
1598
- return normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off";
1599
- };
1600
- var autoSyncStrictEnvKey = "DOCKER_GIT_STATE_AUTO_SYNC_STRICT";
1601
- var defaultSyncMessage = "chore(state): sync";
1602
- var isAutoSyncEnabled = (envValue, hasRemote) => {
1603
- if (envValue === void 0) return hasRemote;
1604
- if (envValue.trim().length === 0) return hasRemote;
1605
- if (isFalsyEnv(envValue)) return false;
1606
- if (isTruthyEnv(envValue)) return true;
1607
- return true;
1608
- };
1609
- //#endregion
1610
1619
  //#region ../lib/src/usecases/state-repo/git-commands.ts
1611
1620
  var successExitCode = Number(ExitCode(0));
1612
- var gitBaseEnv$1 = { GIT_TERMINAL_PROMPT: "0" };
1621
+ var gitBaseEnv$1 = {
1622
+ GIT_TERMINAL_PROMPT: "0",
1623
+ GIT_AUTHOR_NAME: "docker-git",
1624
+ GIT_AUTHOR_EMAIL: "docker-git@users.noreply.github.com",
1625
+ GIT_COMMITTER_NAME: "docker-git",
1626
+ GIT_COMMITTER_EMAIL: "docker-git@users.noreply.github.com"
1627
+ };
1613
1628
  var git = (cwd, args, env = gitBaseEnv$1) => runCommandWithExitCodes({
1614
1629
  cwd,
1615
1630
  command: "git",
@@ -1641,6 +1656,68 @@ var hasOriginRemote = (root) => Effect.map(gitExitCode(root, [
1641
1656
  "origin"
1642
1657
  ]), (exit) => exit === successExitCode);
1643
1658
  //#endregion
1659
+ //#region ../lib/src/usecases/state-repo/adopt-remote.ts
1660
+ var adoptRemoteHistoryIfOrphan = (root, repoRef, env) => Effect.gen(function* (_) {
1661
+ const fetchExit = yield* _(gitExitCode(root, [
1662
+ "fetch",
1663
+ "origin",
1664
+ repoRef
1665
+ ], env));
1666
+ if (fetchExit !== successExitCode) {
1667
+ yield* _(Effect.logWarning(`git fetch origin ${repoRef} failed (exit ${fetchExit}); starting fresh history`));
1668
+ return;
1669
+ }
1670
+ const remoteRef = `origin/${repoRef}`;
1671
+ if ((yield* _(gitExitCode(root, [
1672
+ "show-ref",
1673
+ "--verify",
1674
+ "--quiet",
1675
+ `refs/remotes/${remoteRef}`
1676
+ ], env))) !== successExitCode) return;
1677
+ if ((yield* _(gitExitCode(root, ["rev-parse", "HEAD"], env))) !== successExitCode) {
1678
+ yield* _(git(root, ["reset", remoteRef], env));
1679
+ yield* _(gitExitCode(root, ["checkout-index", "--all"], env));
1680
+ yield* _(Effect.log(`Adopted remote history from ${remoteRef}`));
1681
+ return;
1682
+ }
1683
+ if ((yield* _(gitExitCode(root, [
1684
+ "merge-base",
1685
+ "HEAD",
1686
+ remoteRef
1687
+ ], env))) === successExitCode) return;
1688
+ yield* _(Effect.logWarning(`Local history has no common ancestor with ${remoteRef}; merging unrelated histories`));
1689
+ if ((yield* _(gitExitCode(root, [
1690
+ "merge",
1691
+ "--allow-unrelated-histories",
1692
+ "--no-edit",
1693
+ remoteRef
1694
+ ], env))) === successExitCode) {
1695
+ yield* _(Effect.log(`Merged unrelated histories from ${remoteRef}`));
1696
+ return;
1697
+ }
1698
+ yield* _(gitExitCode(root, ["merge", "--abort"], env));
1699
+ yield* _(Effect.logWarning(`Merge conflict with ${remoteRef}; sync will open a PR for manual resolution`));
1700
+ });
1701
+ //#endregion
1702
+ //#region ../lib/src/usecases/state-repo/env.ts
1703
+ var isTruthyEnv = (value) => {
1704
+ const normalized = value.trim().toLowerCase();
1705
+ return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
1706
+ };
1707
+ var isFalsyEnv = (value) => {
1708
+ const normalized = value.trim().toLowerCase();
1709
+ return normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off";
1710
+ };
1711
+ var autoSyncStrictEnvKey = "DOCKER_GIT_STATE_AUTO_SYNC_STRICT";
1712
+ var defaultSyncMessage = "chore(state): sync";
1713
+ var isAutoSyncEnabled = (envValue, hasRemote) => {
1714
+ if (envValue === void 0) return hasRemote;
1715
+ if (envValue.trim().length === 0) return hasRemote;
1716
+ if (isFalsyEnv(envValue)) return false;
1717
+ if (isTruthyEnv(envValue)) return true;
1718
+ return true;
1719
+ };
1720
+ //#endregion
1644
1721
  //#region ../lib/src/usecases/state-repo/github-auth.ts
1645
1722
  var githubTokenKey = "GITHUB_TOKEN";
1646
1723
  var githubHttpsRemoteRe = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/;
@@ -2901,6 +2978,143 @@ if [[ -f "$LEGACY_AGENTS_PATH" && -f "$AGENTS_PATH" ]]; then
2901
2978
  fi`;
2902
2979
  var renderEntrypointAgentsNotice = (config) => entrypointAgentsNoticeTemplate.replaceAll("__CODEX_HOME__", config.codexHome).replaceAll("__SSH_USER__", config.sshUser).replaceAll("__TARGET_DIR__", config.targetDir);
2903
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
2904
3118
  //#region ../lib/src/core/templates-entrypoint/git.ts
2905
3119
  var renderAuthLabelResolution = () => String.raw`# 2) Ensure GitHub auth vars are available for SSH sessions.
2906
3120
  # Prefer a label-selected token (same selection model as clone/create) when present.
@@ -3803,6 +4017,7 @@ var renderEntrypoint = (config) => [
3803
4017
  renderEntrypointDockerSocket(config),
3804
4018
  renderEntrypointGitConfig(config),
3805
4019
  renderEntrypointClaudeConfig(config),
4020
+ renderEntrypointGeminiConfig(config),
3806
4021
  renderEntrypointGitHooks(),
3807
4022
  renderEntrypointBackgroundTasks(config),
3808
4023
  renderEntrypointBaseline(),
@@ -4359,7 +4574,7 @@ var resolveOriginPushTarget = (originUrl) => {
4359
4574
  const trimmed = originUrl?.trim() ?? "";
4360
4575
  return trimmed.length > 0 ? trimmed : "origin";
4361
4576
  };
4362
- var resolveSyncMessage = (value) => {
4577
+ var resolveSyncMessage$1 = (value) => {
4363
4578
  const trimmed = value?.trim() ?? "";
4364
4579
  return trimmed.length > 0 ? trimmed : defaultSyncMessage;
4365
4580
  };
@@ -4378,7 +4593,7 @@ var commitAllIfNeeded = (root, message, env) => Effect.gen(function* (_) {
4378
4593
  ], env));
4379
4594
  });
4380
4595
  var sanitizeBranchComponent = (value) => value.trim().replaceAll(" ", "-").replaceAll(":", "-").replaceAll("..", "-").replaceAll("@{", "-").replaceAll("\\", "-").replaceAll("^", "-").replaceAll("~", "-");
4381
- var rebaseOntoOriginIfPossible = (root, baseBranch, env) => Effect.gen(function* (_) {
4596
+ var pullRemoteAndRestoreLocal = (root, baseBranch, env) => Effect.gen(function* (_) {
4382
4597
  const fetchExit = yield* _(gitExitCode(root, [
4383
4598
  "fetch",
4384
4599
  "origin",
@@ -4393,10 +4608,26 @@ var rebaseOntoOriginIfPossible = (root, baseBranch, env) => Effect.gen(function*
4393
4608
  "--verify",
4394
4609
  "--quiet",
4395
4610
  `refs/remotes/origin/${baseBranch}`
4396
- ], env))) !== successExitCode) return "skipped";
4397
- if ((yield* _(gitExitCode(root, ["rebase", `origin/${baseBranch}`], env))) === successExitCode) return "ok";
4398
- yield* _(gitExitCode(root, ["rebase", "--abort"], env));
4399
- return "conflict";
4611
+ ], env))) !== successExitCode) return;
4612
+ yield* _(git(root, ["add", "-A"], env));
4613
+ const stashExit = yield* _(gitExitCode(root, ["stash", "--include-untracked"], env));
4614
+ yield* _(git(root, [
4615
+ "reset",
4616
+ "--hard",
4617
+ `origin/${baseBranch}`
4618
+ ], env));
4619
+ if (stashExit === successExitCode) {
4620
+ if ((yield* _(gitExitCode(root, ["stash", "pop"], env))) !== successExitCode) {
4621
+ yield* _(gitExitCode(root, [
4622
+ "checkout",
4623
+ "--theirs",
4624
+ "--",
4625
+ "."
4626
+ ], env));
4627
+ yield* _(git(root, ["add", "-A"], env));
4628
+ yield* _(gitExitCode(root, ["stash", "drop"], env));
4629
+ }
4630
+ }
4400
4631
  });
4401
4632
  var pushToNewBranch = (root, baseBranch, originPushTarget, env) => Effect.gen(function* (_) {
4402
4633
  const headShort = yield* _(gitCapture$1(root, [
@@ -4422,15 +4653,9 @@ var getCurrentBranch = (root, env) => gitCapture$1(root, [
4422
4653
  var runStateSyncOps = (root, originUrl, message, env, options) => Effect.gen(function* (_) {
4423
4654
  const originPushTarget = resolveOriginPushTarget(options?.originPushUrlOverride ?? null);
4424
4655
  yield* _(normalizeLegacyStateProjects(root));
4425
- yield* _(commitAllIfNeeded(root, resolveSyncMessage(message), env));
4426
4656
  const baseBranch = resolveBaseBranch(yield* _(getCurrentBranch(root, env)));
4427
- if ((yield* _(rebaseOntoOriginIfPossible(root, baseBranch, env))) === "conflict") {
4428
- const prBranch = yield* _(pushToNewBranch(root, baseBranch, originPushTarget, env));
4429
- const compareUrl = tryBuildGithubCompareUrl(originUrl, baseBranch, prBranch);
4430
- yield* _(Effect.logWarning(`State sync needs manual merge: pushed changes to branch '${prBranch}'.`));
4431
- yield* _(logOpenPr(originUrl, baseBranch, prBranch, compareUrl));
4432
- return;
4433
- }
4657
+ yield* _(pullRemoteAndRestoreLocal(root, baseBranch, env));
4658
+ yield* _(commitAllIfNeeded(root, resolveSyncMessage$1(message), env));
4434
4659
  const pushExit = yield* _(gitExitCode(root, [
4435
4660
  "push",
4436
4661
  "--no-verify",
@@ -4517,7 +4742,7 @@ var autoSyncState = (message) => Effect.gen(function* (_) {
4517
4742
  onFailure: (error) => Effect.logWarning(`State auto-sync failed: ${String(error)}`),
4518
4743
  onSuccess: () => Effect.void
4519
4744
  }), Effect.asVoid);
4520
- var cloneStateRepo = (root, input) => Effect.gen(function* (_) {
4745
+ var cloneStateRepo = (root, input, env) => Effect.gen(function* (_) {
4521
4746
  const cloneBranchExit = yield* _(runCommandExitCode({
4522
4747
  cwd: root,
4523
4748
  command: "git",
@@ -4528,7 +4753,7 @@ var cloneStateRepo = (root, input) => Effect.gen(function* (_) {
4528
4753
  input.repoUrl,
4529
4754
  root
4530
4755
  ],
4531
- env: gitBaseEnv$1
4756
+ env
4532
4757
  }));
4533
4758
  if (cloneBranchExit === successExitCode) return;
4534
4759
  yield* _(Effect.logWarning(`git clone --branch ${input.repoRef} failed (exit ${cloneBranchExit}); retrying without --branch`));
@@ -4540,58 +4765,63 @@ var cloneStateRepo = (root, input) => Effect.gen(function* (_) {
4540
4765
  input.repoUrl,
4541
4766
  root
4542
4767
  ],
4543
- env: gitBaseEnv$1
4768
+ env
4544
4769
  }));
4545
4770
  if (cloneDefaultExit !== successExitCode) return yield* _(Effect.fail(new CommandFailedError({
4546
4771
  command: "git clone",
4547
4772
  exitCode: cloneDefaultExit
4548
4773
  })));
4549
4774
  }).pipe(Effect.asVoid);
4550
- var initRepoIfNeeded = (fs, path, root, input) => Effect.gen(function* (_) {
4775
+ var initRepoIfNeeded = (fs, path, root, input, env) => Effect.gen(function* (_) {
4551
4776
  yield* _(fs.makeDirectory(root, { recursive: true }));
4552
4777
  const gitDir = path.join(root, ".git");
4553
4778
  if (yield* _(fs.exists(gitDir))) return;
4554
4779
  if ((yield* _(fs.readDirectory(root))).length === 0) {
4555
- yield* _(cloneStateRepo(root, input));
4780
+ yield* _(cloneStateRepo(root, input, env));
4556
4781
  yield* _(Effect.log(`State dir cloned: ${root}`));
4557
4782
  return;
4558
4783
  }
4559
- yield* _(git(root, ["init"], gitBaseEnv$1));
4784
+ yield* _(git(root, ["init", "--initial-branch=main"], env));
4560
4785
  }).pipe(Effect.asVoid);
4561
- var ensureOriginRemote = (root, repoUrl) => Effect.gen(function* (_) {
4786
+ var ensureOriginRemote = (root, repoUrl, env) => Effect.gen(function* (_) {
4562
4787
  if ((yield* _(gitExitCode(root, [
4563
4788
  "remote",
4564
4789
  "set-url",
4565
4790
  "origin",
4566
4791
  repoUrl
4567
- ], gitBaseEnv$1))) === successExitCode) return;
4792
+ ], env))) === successExitCode) return;
4568
4793
  yield* _(git(root, [
4569
4794
  "remote",
4570
4795
  "add",
4571
4796
  "origin",
4572
4797
  repoUrl
4573
- ], gitBaseEnv$1));
4798
+ ], env));
4574
4799
  });
4575
- var checkoutBranchBestEffort = (root, repoRef) => Effect.gen(function* (_) {
4800
+ var checkoutBranchBestEffort = (root, repoRef, env) => Effect.gen(function* (_) {
4576
4801
  const checkoutExit = yield* _(gitExitCode(root, [
4577
4802
  "checkout",
4578
4803
  "-B",
4579
4804
  repoRef
4580
- ], gitBaseEnv$1));
4805
+ ], env));
4581
4806
  if (checkoutExit === successExitCode) return;
4582
4807
  yield* _(Effect.logWarning(`git checkout -B ${repoRef} failed (exit ${checkoutExit})`));
4583
4808
  });
4584
- var stateInit = (input) => Effect.gen(function* (_) {
4585
- const fs = yield* _(FileSystem.FileSystem);
4586
- const path = yield* _(Path.Path);
4587
- const root = resolveStateRoot(path, process.cwd());
4588
- yield* _(initRepoIfNeeded(fs, path, root, input));
4589
- yield* _(ensureOriginRemote(root, input.repoUrl));
4590
- yield* _(checkoutBranchBestEffort(root, input.repoRef));
4591
- yield* _(ensureStateGitignore(fs, path, root));
4592
- yield* _(Effect.log(`State dir ready: ${root}`));
4593
- yield* _(Effect.log(`Remote: ${input.repoUrl}`));
4594
- }).pipe(Effect.asVoid);
4809
+ var stateInit = (input) => {
4810
+ const doInit = (env) => Effect.gen(function* (_) {
4811
+ const fs = yield* _(FileSystem.FileSystem);
4812
+ const path = yield* _(Path.Path);
4813
+ const root = resolveStateRoot(path, process.cwd());
4814
+ yield* _(initRepoIfNeeded(fs, path, root, input, env));
4815
+ yield* _(ensureOriginRemote(root, input.repoUrl, env));
4816
+ yield* _(adoptRemoteHistoryIfOrphan(root, input.repoRef, env));
4817
+ yield* _(checkoutBranchBestEffort(root, input.repoRef, env));
4818
+ yield* _(ensureStateGitignore(fs, path, root));
4819
+ yield* _(Effect.log(`State dir ready: ${root}`));
4820
+ yield* _(Effect.log(`Remote: ${input.repoUrl}`));
4821
+ }).pipe(Effect.asVoid);
4822
+ const token = input.token?.trim() ?? "";
4823
+ return token.length > 0 && isGithubHttpsRemote(input.repoUrl) ? withGithubAskpassEnv(token, doInit) : doInit(gitBaseEnv$1);
4824
+ };
4595
4825
  var stateStatus = Effect.gen(function* (_) {
4596
4826
  const output = yield* _(gitCapture$1(resolveStateRoot(yield* _(Path.Path), process.cwd()), [
4597
4827
  "status",
@@ -5838,12 +6068,7 @@ var applyProjectConfig = (command) => runApplyForProjectDir(command.projectDir,
5838
6068
  return yield* _(runApplyForProjectDir(inferredProjectDir, command));
5839
6069
  }) : Effect.fail(error)));
5840
6070
  //#endregion
5841
- //#region ../lib/src/usecases/auth-claude-oauth.ts
5842
- var oauthTokenEnvKey = "DOCKER_GIT_CLAUDE_OAUTH_TOKEN";
5843
- var tokenMarker = "Your OAuth token (valid for 1 year):";
5844
- var tokenFooterMarker = "Store this token securely.";
5845
- var outputWindowSize = 262144;
5846
- var oauthTokenRegex = /([A-Za-z0-9][A-Za-z0-9._-]{20,})/u;
6071
+ //#region ../lib/src/shell/ansi-strip.ts
5847
6072
  var ansiEscape = "\x1B";
5848
6073
  var ansiBell = "\x07";
5849
6074
  var isAnsiFinalByte = (codePoint) => codePoint !== void 0 && codePoint >= 64 && codePoint <= 126;
@@ -5887,6 +6112,20 @@ var stripAnsi = (raw) => {
5887
6112
  }
5888
6113
  return cleaned.join("");
5889
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;
5890
6129
  var extractOauthToken = (rawOutput) => {
5891
6130
  const normalized = stripAnsi(rawOutput).replaceAll("\r", "\n");
5892
6131
  const markerIndex = normalized.lastIndexOf(tokenMarker);
@@ -5941,21 +6180,14 @@ var buildDockerSetupTokenArgs = (spec) => {
5941
6180
  ...spec.args
5942
6181
  ];
5943
6182
  };
5944
- 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")));
5945
- var writeChunkToFd = (fd, chunk) => {
5946
- if (fd === 2) {
5947
- process.stderr.write(chunk);
5948
- return;
5949
- }
5950
- process.stdout.write(chunk);
5951
- };
5952
- 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) => {
5953
6185
  const decoder = new TextDecoder("utf-8");
5954
6186
  let outputWindow = "";
5955
6187
  return pipe(source, Stream.runForEach((chunk) => Effect.sync(() => {
5956
6188
  writeChunkToFd(fd, chunk);
5957
6189
  outputWindow += decoder.decode(chunk);
5958
- if (outputWindow.length > outputWindowSize) outputWindow = outputWindow.slice(-outputWindowSize);
6190
+ if (outputWindow.length > outputWindowSize$1) outputWindow = outputWindow.slice(-outputWindowSize$1);
5959
6191
  if (tokenBox.value !== null) return;
5960
6192
  const parsed = extractOauthToken(outputWindow);
5961
6193
  if (parsed !== null) tokenBox.value = parsed;
@@ -5977,10 +6209,10 @@ var runClaudeOauthLoginWithPrompt = (cwd, accountPath, options) => {
5977
6209
  const envToken = oauthTokenFromEnv();
5978
6210
  if (envToken !== null) return ensureOauthToken(envToken);
5979
6211
  return Effect.scoped(Effect.gen(function* (_) {
5980
- 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)));
5981
6213
  const tokenBox = { value: null };
5982
- const stdoutFiber = yield* _(Effect.forkScoped(pumpDockerOutput(proc.stdout, 1, tokenBox)));
5983
- 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)));
5984
6216
  const exitCode = yield* _(proc.exitCode.pipe(Effect.map(Number)));
5985
6217
  yield* _(Fiber$1.join(stdoutFiber));
5986
6218
  yield* _(Fiber$1.join(stderrFiber));
@@ -6001,19 +6233,15 @@ var claudeOauthTokenPath = (accountPath) => `${accountPath}/${claudeOauthTokenFi
6001
6233
  var claudeConfigPath = (accountPath) => `${accountPath}/${claudeConfigFileName}`;
6002
6234
  var claudeCredentialsPath = (accountPath) => `${accountPath}/${claudeCredentialsFileName}`;
6003
6235
  var claudeNestedCredentialsPath = (accountPath) => `${accountPath}/${claudeCredentialsDirName}/${claudeCredentialsFileName}`;
6004
- var isRegularFile = (fs, filePath) => Effect.gen(function* (_) {
6005
- if (!(yield* _(fs.exists(filePath)))) return false;
6006
- return (yield* _(fs.stat(filePath))).type === "File";
6007
- });
6008
6236
  var syncClaudeCredentialsFile = (fs, accountPath) => Effect.gen(function* (_) {
6009
6237
  const nestedPath = claudeNestedCredentialsPath(accountPath);
6010
6238
  const rootPath = claudeCredentialsPath(accountPath);
6011
- if (yield* _(isRegularFile(fs, nestedPath))) {
6239
+ if (yield* _(isRegularFile$1(fs, nestedPath))) {
6012
6240
  yield* _(fs.copyFile(nestedPath, rootPath));
6013
6241
  yield* _(fs.chmod(rootPath, 384), Effect.orElseSucceed(() => void 0));
6014
6242
  return;
6015
6243
  }
6016
- if (yield* _(isRegularFile(fs, rootPath))) {
6244
+ if (yield* _(isRegularFile$1(fs, rootPath))) {
6017
6245
  const nestedDirPath = `${accountPath}/${claudeCredentialsDirName}`;
6018
6246
  yield* _(fs.makeDirectory(nestedDirPath, { recursive: true }));
6019
6247
  yield* _(fs.copyFile(rootPath, nestedPath));
@@ -6026,12 +6254,12 @@ var clearClaudeSessionCredentials = (fs, accountPath) => Effect.gen(function* (_
6026
6254
  });
6027
6255
  var hasNonEmptyOauthToken$1 = (fs, accountPath) => Effect.gen(function* (_) {
6028
6256
  const tokenPath = claudeOauthTokenPath(accountPath);
6029
- if (!(yield* _(isRegularFile(fs, tokenPath)))) return false;
6257
+ if (!(yield* _(isRegularFile$1(fs, tokenPath)))) return false;
6030
6258
  return (yield* _(fs.readFileString(tokenPath), Effect.orElseSucceed(() => ""))).trim().length > 0;
6031
6259
  });
6032
6260
  var readOauthToken = (fs, accountPath) => Effect.gen(function* (_) {
6033
6261
  const tokenPath = claudeOauthTokenPath(accountPath);
6034
- if (!(yield* _(isRegularFile(fs, tokenPath)))) return null;
6262
+ if (!(yield* _(isRegularFile$1(fs, tokenPath)))) return null;
6035
6263
  const token = (yield* _(fs.readFileString(tokenPath), Effect.orElseSucceed(() => ""))).trim();
6036
6264
  return token.length > 0 ? token : null;
6037
6265
  });
@@ -6041,7 +6269,7 @@ var resolveClaudeAuthMethod = (fs, accountPath) => Effect.gen(function* (_) {
6041
6269
  return "oauth-token";
6042
6270
  }
6043
6271
  yield* _(syncClaudeCredentialsFile(fs, accountPath));
6044
- return (yield* _(isRegularFile(fs, claudeCredentialsPath(accountPath)))) ? "claude-ai-session" : "none";
6272
+ return (yield* _(isRegularFile$1(fs, claudeCredentialsPath(accountPath)))) ? "claude-ai-session" : "none";
6045
6273
  });
6046
6274
  var buildClaudeAuthEnv = (interactive, oauthToken = null) => [...interactive ? [
6047
6275
  `HOME=${claudeContainerHomeDir}`,
@@ -6266,53 +6494,387 @@ var authCodexStatus = (command) => withCodexAuth(command, ({ accountPath, cwd })
6266
6494
  }));
6267
6495
  var authCodexLogout = (command) => withCodexAuth(command, ({ accountPath, cwd }) => runCodexLogout(cwd, accountPath)).pipe(Effect.zipRight(autoSyncState(`chore(state): auth codex logout ${normalizeAccountLabel(command.label, "default")}`)));
6268
6496
  //#endregion
6269
- //#region ../lib/src/usecases/auth-github.ts
6270
- var ensureGithubOrchLayout = (cwd, envGlobalPath) => migrateLegacyOrchLayout(cwd, {
6271
- envGlobalPath,
6272
- envProjectPath: defaultTemplateConfig.envProjectPath,
6273
- codexAuthPath: defaultTemplateConfig.codexAuthPath,
6274
- ghAuthPath: ghAuthRoot,
6275
- claudeAuthPath: ".docker-git/.orch/auth/claude"
6276
- });
6277
- var normalizeGithubLabel = (value) => {
6278
- const trimmed = value?.trim() ?? "";
6279
- if (trimmed.length === 0) return "";
6280
- const cleaned = trimRightChar(trimLeftChar(trimmed.toUpperCase().replaceAll(/[^A-Z0-9]+/g, "_"), "_"), "_");
6281
- return cleaned.length > 0 ? cleaned : "";
6282
- };
6283
- var tokenKey = "GITHUB_TOKEN";
6284
- var tokenPrefix = "GITHUB_TOKEN__";
6285
- var buildGithubTokenKey = (label) => {
6286
- const normalized = normalizeGithubLabel(label);
6287
- if (normalized === "DEFAULT" || normalized.length === 0) return tokenKey;
6288
- return `${tokenPrefix}${normalized}`;
6289
- };
6290
- var labelFromKey = (key) => key.startsWith(tokenPrefix) ? key.slice(14) : "default";
6291
- var listGithubTokens = (envText) => parseEnvEntries(envText).filter((entry) => entry.key === tokenKey || entry.key.startsWith(tokenPrefix)).map((entry) => ({
6292
- key: entry.key,
6293
- label: labelFromKey(entry.key),
6294
- token: entry.value
6295
- })).filter((entry) => entry.token.trim().length > 0);
6296
- var defaultGithubScopes = "repo,workflow,read:org";
6297
- var normalizeGithubScopes = (value) => {
6298
- const raw = value?.trim() ?? "";
6299
- const scopes = (raw.length === 0 ? defaultGithubScopes : raw).split(/[,\s]+/g).map((scope) => scope.trim()).filter((scope) => scope.length > 0 && scope !== "delete_repo");
6300
- return scopes.length === 0 ? defaultGithubScopes.split(",") : scopes;
6301
- };
6302
- var withEnvContext = (envGlobalPath, run) => withFsPathContext(({ cwd, fs, path }) => Effect.gen(function* (_) {
6303
- yield* _(ensureGithubOrchLayout(cwd, envGlobalPath));
6304
- const envPath = resolvePathFromCwd(path, cwd, envGlobalPath);
6305
- return yield* _(run({
6306
- fs,
6307
- envPath,
6308
- current: yield* _(readEnvText(fs, envPath))
6309
- }));
6310
- }));
6311
- var resolveGithubTokenFromGh = (cwd, accountPath) => runDockerAuthCapture(buildDockerAuthSpec({
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) => ({
6312
6521
  cwd,
6313
- image: ghImageName,
6522
+ image,
6314
6523
  hostPath: accountPath,
6315
- containerPath: ghAuthDir,
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
6750
+ //#region ../lib/src/usecases/state-repo-github.ts
6751
+ var dotDockerGitRepoName = ".docker-git";
6752
+ var defaultStateRef = "main";
6753
+ var resolveViewerLogin = (cwd, hostPath, token) => Effect.gen(function* (_) {
6754
+ const raw = yield* _(runGhApiCapture(cwd, hostPath, token, [
6755
+ "/user",
6756
+ "--jq",
6757
+ ".login"
6758
+ ]));
6759
+ if (raw.length === 0) return yield* _(Effect.fail(new CommandFailedError({
6760
+ command: "gh api /user --jq .login",
6761
+ exitCode: 1
6762
+ })));
6763
+ return raw;
6764
+ });
6765
+ var getRepoCloneUrl = (cwd, hostPath, token, login) => runGhApiNullable(cwd, hostPath, token, [
6766
+ `/repos/${login}/${dotDockerGitRepoName}`,
6767
+ "--jq",
6768
+ ".clone_url"
6769
+ ]);
6770
+ var createStateRepo = (cwd, hostPath, token) => runGhApiNullable(cwd, hostPath, token, [
6771
+ "-X",
6772
+ "POST",
6773
+ "/user/repos",
6774
+ "-f",
6775
+ `name=${dotDockerGitRepoName}`,
6776
+ "-f",
6777
+ "private=false",
6778
+ "-f",
6779
+ "auto_init=true",
6780
+ "--jq",
6781
+ ".clone_url"
6782
+ ]);
6783
+ /**
6784
+ * Ensures the .docker-git state repository exists on GitHub and is initialised locally.
6785
+ *
6786
+ * On GitHub auth, immediately:
6787
+ * 1. Resolve the authenticated user's login via the GitHub API
6788
+ * 2. Check whether `<login>/.docker-git` exists on GitHub
6789
+ * 3. If missing, create the repository (public, auto-initialised with a README)
6790
+ * 4. Initialise the local `~/.docker-git` directory as a clone of that repository
6791
+ *
6792
+ * All failures are swallowed and logged as warnings so they never abort the auth
6793
+ * flow itself.
6794
+ *
6795
+ * @param token - A valid GitHub personal-access or OAuth token
6796
+ * @returns Effect<void, never, GithubStateRepoRuntime>
6797
+ *
6798
+ * @pure false
6799
+ * @effect FileSystem, CommandExecutor (Docker gh CLI, git)
6800
+ * @invariant ∀token ∈ ValidTokens: ensureStateDotDockerGitRepo(token) → cloned(~/.docker-git) ∨ warned
6801
+ * @precondition token.length > 0
6802
+ * @postcondition ~/.docker-git is a git repo with origin pointing to github.com/<login>/.docker-git
6803
+ * @complexity O(1) API calls
6804
+ * @throws Never - all errors are caught and logged
6805
+ */
6806
+ var ensureStateDotDockerGitRepo = (token) => withFsPathContext(({ cwd, fs, path }) => Effect.gen(function* (_) {
6807
+ const ghRoot = resolvePathFromCwd(path, cwd, ghAuthRoot);
6808
+ yield* _(fs.makeDirectory(ghRoot, { recursive: true }));
6809
+ yield* _(ensureGhAuthImage(fs, path, cwd, "gh api"));
6810
+ const login = yield* _(resolveViewerLogin(cwd, ghRoot, token));
6811
+ let cloneUrl = yield* _(getRepoCloneUrl(cwd, ghRoot, token, login));
6812
+ if (cloneUrl === null) {
6813
+ yield* _(Effect.log(`Creating .docker-git repository for ${login}...`));
6814
+ cloneUrl = yield* _(createStateRepo(cwd, ghRoot, token));
6815
+ }
6816
+ if (cloneUrl === null) {
6817
+ yield* _(Effect.logWarning(`Could not resolve or create .docker-git repository for ${login}`));
6818
+ return;
6819
+ }
6820
+ yield* _(Effect.log(`Initializing state repository: ${cloneUrl}`));
6821
+ yield* _(stateInit({
6822
+ repoUrl: cloneUrl,
6823
+ repoRef: defaultStateRef,
6824
+ token
6825
+ }));
6826
+ })).pipe(Effect.matchEffect({
6827
+ onFailure: (error) => Effect.logWarning(`State repo setup failed: ${error instanceof Error ? error.message : String(error)}`),
6828
+ onSuccess: () => Effect.void
6829
+ }));
6830
+ //#endregion
6831
+ //#region ../lib/src/usecases/auth-github.ts
6832
+ var ensureGithubOrchLayout = (cwd, envGlobalPath) => migrateLegacyOrchLayout(cwd, {
6833
+ envGlobalPath,
6834
+ envProjectPath: defaultTemplateConfig.envProjectPath,
6835
+ codexAuthPath: defaultTemplateConfig.codexAuthPath,
6836
+ ghAuthPath: ghAuthRoot,
6837
+ claudeAuthPath: ".docker-git/.orch/auth/claude"
6838
+ });
6839
+ var normalizeGithubLabel = (value) => {
6840
+ const trimmed = value?.trim() ?? "";
6841
+ if (trimmed.length === 0) return "";
6842
+ const cleaned = trimRightChar(trimLeftChar(trimmed.toUpperCase().replaceAll(/[^A-Z0-9]+/g, "_"), "_"), "_");
6843
+ return cleaned.length > 0 ? cleaned : "";
6844
+ };
6845
+ var tokenKey = "GITHUB_TOKEN";
6846
+ var tokenPrefix = "GITHUB_TOKEN__";
6847
+ var buildGithubTokenKey = (label) => {
6848
+ const normalized = normalizeGithubLabel(label);
6849
+ if (normalized === "DEFAULT" || normalized.length === 0) return tokenKey;
6850
+ return `${tokenPrefix}${normalized}`;
6851
+ };
6852
+ var labelFromKey = (key) => key.startsWith(tokenPrefix) ? key.slice(14) : "default";
6853
+ var listGithubTokens = (envText) => parseEnvEntries(envText).filter((entry) => entry.key === tokenKey || entry.key.startsWith(tokenPrefix)).map((entry) => ({
6854
+ key: entry.key,
6855
+ label: labelFromKey(entry.key),
6856
+ token: entry.value
6857
+ })).filter((entry) => entry.token.trim().length > 0);
6858
+ var defaultGithubScopes = "repo,workflow,read:org";
6859
+ var normalizeGithubScopes = (value) => {
6860
+ const raw = value?.trim() ?? "";
6861
+ const scopes = (raw.length === 0 ? defaultGithubScopes : raw).split(/[,\s]+/g).map((scope) => scope.trim()).filter((scope) => scope.length > 0 && scope !== "delete_repo");
6862
+ return scopes.length === 0 ? defaultGithubScopes.split(",") : scopes;
6863
+ };
6864
+ var withEnvContext = (envGlobalPath, run) => withFsPathContext(({ cwd, fs, path }) => Effect.gen(function* (_) {
6865
+ yield* _(ensureGithubOrchLayout(cwd, envGlobalPath));
6866
+ const envPath = resolvePathFromCwd(path, cwd, envGlobalPath);
6867
+ return yield* _(run({
6868
+ fs,
6869
+ envPath,
6870
+ current: yield* _(readEnvText(fs, envPath))
6871
+ }));
6872
+ }));
6873
+ var resolveGithubTokenFromGh = (cwd, accountPath) => runDockerAuthCapture(buildDockerAuthSpec({
6874
+ cwd,
6875
+ image: ghImageName,
6876
+ hostPath: accountPath,
6877
+ containerPath: ghAuthDir,
6316
6878
  env: `GH_CONFIG_DIR=${ghAuthDir}`,
6317
6879
  args: ["auth", "token"],
6318
6880
  interactive: false
@@ -6363,6 +6925,7 @@ var runGithubInteractiveLogin = (cwd, fs, path, envPath, command) => Effect.gen(
6363
6925
  const resolved = yield* _(resolveGithubTokenFromGh(cwd, accountPath));
6364
6926
  yield* _(ensureEnvFile$1(fs, path, envPath));
6365
6927
  yield* _(persistGithubToken(fs, envPath, buildGithubTokenKey(command.label), resolved));
6928
+ return resolved;
6366
6929
  });
6367
6930
  var authGithubLogin = (command) => withFsPathContext(({ cwd, fs, path }) => Effect.gen(function* (_) {
6368
6931
  yield* _(ensureGithubOrchLayout(cwd, command.envGlobalPath));
@@ -6373,10 +6936,11 @@ var authGithubLogin = (command) => withFsPathContext(({ cwd, fs, path }) => Effe
6373
6936
  if (token.length > 0) {
6374
6937
  yield* _(ensureEnvFile$1(fs, path, envPath));
6375
6938
  yield* _(persistGithubToken(fs, envPath, key, token));
6939
+ yield* _(ensureStateDotDockerGitRepo(token));
6376
6940
  yield* _(autoSyncState(`chore(state): auth gh ${label}`));
6377
6941
  return;
6378
6942
  }
6379
- yield* _(runGithubInteractiveLogin(cwd, fs, path, envPath, command));
6943
+ yield* _(ensureStateDotDockerGitRepo(yield* _(runGithubInteractiveLogin(cwd, fs, path, envPath, command))));
6380
6944
  yield* _(autoSyncState(`chore(state): auth gh ${label}`));
6381
6945
  }));
6382
6946
  var authGithubStatus = (command) => withEnvContext(command.envGlobalPath, ({ current, envPath }) => Effect.gen(function* (_) {
@@ -7480,10 +8044,12 @@ var normalizeLabel$1 = (value) => {
7480
8044
  var defaultEnvGlobalPath = ".docker-git/.orch/env/global.env";
7481
8045
  var defaultCodexAuthPath = ".docker-git/.orch/auth/codex";
7482
8046
  var defaultClaudeAuthPath = ".docker-git/.orch/auth/claude";
8047
+ var defaultGeminiAuthPath = ".docker-git/.orch/auth/gemini";
7483
8048
  var resolveAuthOptions = (raw) => ({
7484
8049
  envGlobalPath: raw.envGlobalPath ?? defaultEnvGlobalPath,
7485
8050
  codexAuthPath: raw.codexAuthPath ?? defaultCodexAuthPath,
7486
8051
  claudeAuthPath: defaultClaudeAuthPath,
8052
+ geminiAuthPath: defaultGeminiAuthPath,
7487
8053
  label: normalizeLabel$1(raw.label),
7488
8054
  token: normalizeLabel$1(raw.token),
7489
8055
  scopes: normalizeLabel$1(raw.scopes),
@@ -7529,7 +8095,20 @@ var buildClaudeCommand = (action, options) => Match.value(action).pipe(Match.whe
7529
8095
  label: options.label,
7530
8096
  claudeAuthPath: options.claudeAuthPath
7531
8097
  })), Match.orElse(() => Either.left(invalidArgument("auth action", `unknown action '${action}'`))));
7532
- 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}'`))));
7533
8112
  var parseAuth = (args) => {
7534
8113
  if (args.length < 2) return Either.left(missingArgument(args.length === 0 ? "auth provider" : "auth action"));
7535
8114
  const provider = args[0] ?? "";
@@ -7636,13 +8215,15 @@ var buildDefaultPathConfig = (normalizedSecretsRoot) => normalizedSecretsRoot ==
7636
8215
  authorizedKeysPath: defaultTemplateConfig.authorizedKeysPath,
7637
8216
  envGlobalPath: defaultTemplateConfig.envGlobalPath,
7638
8217
  envProjectPath: defaultTemplateConfig.envProjectPath,
7639
- codexAuthPath: defaultTemplateConfig.codexAuthPath
8218
+ codexAuthPath: defaultTemplateConfig.codexAuthPath,
8219
+ geminiAuthPath: defaultTemplateConfig.geminiAuthPath
7640
8220
  } : {
7641
8221
  dockerGitPath: defaultTemplateConfig.dockerGitPath,
7642
8222
  authorizedKeysPath: defaultTemplateConfig.authorizedKeysPath,
7643
8223
  envGlobalPath: `${normalizedSecretsRoot}/global.env`,
7644
8224
  envProjectPath: defaultTemplateConfig.envProjectPath,
7645
- codexAuthPath: `${normalizedSecretsRoot}/codex`
8225
+ codexAuthPath: `${normalizedSecretsRoot}/codex`,
8226
+ geminiAuthPath: `${normalizedSecretsRoot}/gemini`
7646
8227
  };
7647
8228
  var resolvePaths = (raw, repoPath) => Either.gen(function* (_) {
7648
8229
  const defaults = buildDefaultPathConfig(resolveNormalizedSecretsRoot(raw.secretsRoot));
@@ -7659,6 +8240,8 @@ var resolvePaths = (raw, repoPath) => Either.gen(function* (_) {
7659
8240
  codexAuthPath,
7660
8241
  codexSharedAuthPath: codexAuthPath,
7661
8242
  codexHome: yield* _(nonEmpty("--codex-home", raw.codexHome, defaultTemplateConfig.codexHome)),
8243
+ geminiAuthPath: defaults.geminiAuthPath,
8244
+ geminiHome: defaultTemplateConfig.geminiHome,
7662
8245
  outDir: yield* _(nonEmpty("--out-dir", raw.outDir, `.docker-git/${repoPath}`))
7663
8246
  };
7664
8247
  });
@@ -7688,6 +8271,8 @@ var buildTemplateConfig = ({ agentAuto, agentMode, claudeAuthLabel, codexAuthLab
7688
8271
  codexAuthPath: paths.codexAuthPath,
7689
8272
  codexSharedAuthPath: paths.codexSharedAuthPath,
7690
8273
  codexHome: paths.codexHome,
8274
+ geminiAuthPath: paths.geminiAuthPath,
8275
+ geminiHome: paths.geminiHome,
7691
8276
  cpuLimit,
7692
8277
  ramLimit,
7693
8278
  dockerNetworkMode,
@@ -8769,6 +9354,12 @@ var countAuthAccountDirectories = (fs, path, root) => Effect.gen(function* (_) {
8769
9354
  return count;
8770
9355
  });
8771
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
8772
9363
  //#region src/docker-git/menu-labeled-env.ts
8773
9364
  var normalizeLabel = (value) => {
8774
9365
  const trimmed = value.trim();
@@ -8817,6 +9408,18 @@ var authMenuItems = [
8817
9408
  action: "ClaudeLogout",
8818
9409
  label: "Claude Code: logout (clear cache)"
8819
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
+ },
8820
9423
  {
8821
9424
  action: "Refresh",
8822
9425
  label: "Refresh snapshot"
@@ -8876,34 +9479,62 @@ var flowSteps$1 = {
8876
9479
  label: "Label to logout (empty = default)",
8877
9480
  required: false,
8878
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
8879
9505
  }]
8880
9506
  };
8881
- 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);
8882
- 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);
8883
9509
  var buildGlobalEnvPath$1 = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/env/global.env`;
8884
9510
  var buildClaudeAuthPath$1 = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/auth/claude`;
9511
+ var buildGeminiAuthPath$1 = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/auth/gemini`;
8885
9512
  var loadAuthEnvText = (cwd) => Effect.gen(function* (_) {
8886
9513
  const fs = yield* _(FileSystem.FileSystem);
8887
9514
  const path = yield* _(Path.Path);
8888
9515
  const globalEnvPath = buildGlobalEnvPath$1(cwd);
8889
9516
  const claudeAuthPath = buildClaudeAuthPath$1(cwd);
9517
+ const geminiAuthPath = buildGeminiAuthPath$1(cwd);
8890
9518
  yield* _(ensureEnvFile$1(fs, path, globalEnvPath));
8891
9519
  return {
8892
9520
  fs,
8893
9521
  path,
8894
9522
  globalEnvPath,
8895
9523
  claudeAuthPath,
9524
+ geminiAuthPath,
8896
9525
  envText: yield* _(readEnvText(fs, globalEnvPath))
8897
9526
  };
8898
9527
  });
8899
- 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 }) => ({
8900
9529
  globalEnvPath,
8901
9530
  claudeAuthPath,
9531
+ geminiAuthPath,
8902
9532
  totalEntries: parseEnvEntries(envText).filter((entry) => entry.value.trim().length > 0).length,
8903
9533
  githubTokenEntries: countKeyEntries(envText, "GITHUB_TOKEN"),
8904
9534
  gitTokenEntries: countKeyEntries(envText, "GIT_AUTH_TOKEN"),
8905
9535
  gitUserEntries: countKeyEntries(envText, "GIT_AUTH_USER"),
8906
- claudeAuthEntries
9536
+ claudeAuthEntries,
9537
+ geminiAuthEntries
8907
9538
  })))));
8908
9539
  var writeAuthFlow = (cwd, flow, values) => pipe(loadAuthEnvText(cwd), Effect.flatMap(({ envText, fs, globalEnvPath }) => {
8909
9540
  const label = values["label"] ?? "";
@@ -8932,6 +9563,67 @@ var authMenuActionByIndex = (index) => {
8932
9563
  };
8933
9564
  var authMenuSize = () => authMenuItems.length;
8934
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
8935
9627
  //#region src/docker-git/menu-input-utils.ts
8936
9628
  var parseMenuIndex = (input) => {
8937
9629
  const trimmed = input.trim();
@@ -8988,14 +9680,6 @@ var defaultLabel = (value) => {
8988
9680
  const trimmed = value.trim();
8989
9681
  return trimmed.length > 0 ? trimmed : "default";
8990
9682
  };
8991
- var startAuthMenuWithSnapshot = (snapshot, context) => {
8992
- context.setView({
8993
- _tag: "AuthMenu",
8994
- selected: 0,
8995
- snapshot
8996
- });
8997
- context.setMessage(null);
8998
- };
8999
9683
  var startAuthPrompt = (snapshot, flow, context) => {
9000
9684
  context.setView({
9001
9685
  _tag: "AuthPrompt",
@@ -9007,39 +9691,6 @@ var startAuthPrompt = (snapshot, flow, context) => {
9007
9691
  });
9008
9692
  context.setMessage(null);
9009
9693
  };
9010
- var resolveLabelOption = (values) => {
9011
- const labelValue = (values["label"] ?? "").trim();
9012
- return labelValue.length > 0 ? labelValue : null;
9013
- };
9014
- var resolveAuthPromptEffect = (view, cwd, values) => {
9015
- const labelOption = resolveLabelOption(values);
9016
- return Match.value(view.flow).pipe(Match.when("GithubOauth", () => authGithubLogin({
9017
- _tag: "AuthGithubLogin",
9018
- label: labelOption,
9019
- token: null,
9020
- scopes: null,
9021
- envGlobalPath: view.snapshot.globalEnvPath
9022
- })), Match.when("ClaudeOauth", () => authClaudeLogin({
9023
- _tag: "AuthClaudeLogin",
9024
- label: labelOption,
9025
- claudeAuthPath: claudeAuthRoot
9026
- })), Match.when("ClaudeLogout", () => authClaudeLogout({
9027
- _tag: "AuthClaudeLogout",
9028
- label: labelOption,
9029
- claudeAuthPath: claudeAuthRoot
9030
- })), 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);
9031
- };
9032
- var runAuthPromptEffect = (effect, view, label, context, options) => {
9033
- const withOptionalSuspension = options.suspendTui ? withSuspendedTui(effect, {
9034
- onError: pauseOnError(renderError),
9035
- onResume: resumeSshWithSkipInputs(context)
9036
- }) : effect;
9037
- context.setSshActive(options.suspendTui);
9038
- context.runner.runEffect(pipe(withOptionalSuspension, Effect.zipRight(readAuthSnapshot(context.state.cwd)), Effect.tap((snapshot) => Effect.sync(() => {
9039
- startAuthMenuWithSnapshot(snapshot, context);
9040
- context.setMessage(successMessage$1(view.flow, label));
9041
- })), Effect.asVoid));
9042
- };
9043
9694
  var loadAuthMenuView = (cwd, context) => pipe(readAuthSnapshot(cwd), Effect.tap((snapshot) => Effect.sync(() => {
9044
9695
  startAuthMenuWithSnapshot(snapshot, context);
9045
9696
  })), Effect.asVoid);
@@ -9059,7 +9710,10 @@ var submitAuthPrompt = (view, context) => {
9059
9710
  startAuthMenuWithSnapshot(view.snapshot, context);
9060
9711
  }, (nextValues) => {
9061
9712
  const label = defaultLabel(nextValues["label"] ?? "");
9062
- 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" });
9063
9717
  });
9064
9718
  };
9065
9719
  var setAuthMenuSelection = (view, selected, context) => {
@@ -9101,6 +9755,15 @@ var handleAuthMenuInput = (input, key, view, context) => {
9101
9755
  }
9102
9756
  handleAuthMenuNumberInput(input, view, context);
9103
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
+ };
9104
9767
  var handleAuthPromptInput = (input, key, view, context) => {
9105
9768
  if (key.escape) {
9106
9769
  startAuthMenuWithSnapshot(view.snapshot, context);
@@ -9117,15 +9780,6 @@ var handleAuthPromptInput = (input, key, view, context) => {
9117
9780
  context
9118
9781
  });
9119
9782
  };
9120
- var setAuthPromptBuffer = (args) => {
9121
- const { context, input, key, view } = args;
9122
- const nextBuffer = nextBufferValue(input, key, view.buffer);
9123
- if (nextBuffer === null) return;
9124
- context.setView({
9125
- ...view,
9126
- buffer: nextBuffer
9127
- });
9128
- };
9129
9783
  var openAuthMenu = (context) => {
9130
9784
  context.setMessage("Loading auth profiles...");
9131
9785
  context.runner.runEffect(loadAuthMenuView(context.state.cwd, context));
@@ -9218,15 +9872,17 @@ var loadRuntimeByProject = (items) => pipe(runDockerPsNames(process.cwd()), Effe
9218
9872
  }));
9219
9873
  var runtimeForSelection = (view, selected) => view.runtimeByProject[selected.projectDir] ?? stoppedRuntime$1();
9220
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
9221
9881
  //#region src/docker-git/menu-project-auth-claude.ts
9222
9882
  var oauthTokenFileName = ".oauth-token";
9223
9883
  var legacyConfigFileName = ".config.json";
9224
9884
  var credentialsFileName = ".credentials.json";
9225
9885
  var nestedCredentialsFileName = ".claude/.credentials.json";
9226
- var hasFileAtPath = (fs, filePath) => Effect.gen(function* (_) {
9227
- if (!(yield* _(fs.exists(filePath)))) return false;
9228
- return (yield* _(fs.stat(filePath))).type === "File";
9229
- });
9230
9886
  var hasNonEmptyOauthToken = (fs, tokenPath) => Effect.gen(function* (_) {
9231
9887
  if (!(yield* _(hasFileAtPath(fs, tokenPath)))) return false;
9232
9888
  return (yield* _(fs.readFileString(tokenPath), Effect.orElseSucceed(() => ""))).trim().length > 0;
@@ -9253,6 +9909,107 @@ var hasClaudeAccountCredentials = (fs, accountPath) => hasFileAtPath(fs, `${acco
9253
9909
  }));
9254
9910
  }));
9255
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
9256
10013
  //#region src/docker-git/menu-project-auth-data.ts
9257
10014
  var projectAuthMenuItems = [
9258
10015
  {
@@ -9279,6 +10036,14 @@ var projectAuthMenuItems = [
9279
10036
  action: "ProjectClaudeDisconnect",
9280
10037
  label: "Project: Claude disconnect"
9281
10038
  },
10039
+ {
10040
+ action: "ProjectGeminiConnect",
10041
+ label: "Project: Gemini connect label"
10042
+ },
10043
+ {
10044
+ action: "ProjectGeminiDisconnect",
10045
+ label: "Project: Gemini disconnect"
10046
+ },
9282
10047
  {
9283
10048
  action: "Refresh",
9284
10049
  label: "Refresh snapshot"
@@ -9309,7 +10074,14 @@ var flowSteps = {
9309
10074
  required: false,
9310
10075
  secret: false
9311
10076
  }],
9312
- ProjectClaudeDisconnect: []
10077
+ ProjectClaudeDisconnect: [],
10078
+ ProjectGeminiConnect: [{
10079
+ key: "label",
10080
+ label: "Label (empty = default)",
10081
+ required: false,
10082
+ secret: false
10083
+ }],
10084
+ ProjectGeminiDisconnect: []
9313
10085
  };
9314
10086
  var resolveCanonicalLabel = (value) => {
9315
10087
  const normalized = normalizeLabel(value);
@@ -9317,18 +10089,19 @@ var resolveCanonicalLabel = (value) => {
9317
10089
  };
9318
10090
  var githubTokenBaseKey = "GITHUB_TOKEN";
9319
10091
  var gitTokenBaseKey = "GIT_AUTH_TOKEN";
9320
- var gitUserBaseKey = "GIT_AUTH_USER";
9321
10092
  var projectGithubLabelKey = "GITHUB_AUTH_LABEL";
9322
10093
  var projectGitLabelKey = "GIT_AUTH_LABEL";
9323
10094
  var projectClaudeLabelKey = "CLAUDE_AUTH_LABEL";
9324
- var defaultGitUser = "x-access-token";
10095
+ var projectGeminiLabelKey = "GEMINI_AUTH_LABEL";
9325
10096
  var buildGlobalEnvPath = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/env/global.env`;
9326
10097
  var buildClaudeAuthPath = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/auth/claude`;
10098
+ var buildGeminiAuthPath = (cwd) => `${defaultProjectsRoot(cwd)}/.orch/auth/gemini`;
9327
10099
  var loadProjectAuthEnvText = (project) => Effect.gen(function* (_) {
9328
10100
  const fs = yield* _(FileSystem.FileSystem);
9329
10101
  const path = yield* _(Path.Path);
9330
10102
  const globalEnvPath = buildGlobalEnvPath(process.cwd());
9331
10103
  const claudeAuthPath = buildClaudeAuthPath(process.cwd());
10104
+ const geminiAuthPath = buildGeminiAuthPath(process.cwd());
9332
10105
  yield* _(ensureEnvFile$1(fs, path, globalEnvPath));
9333
10106
  yield* _(ensureEnvFile$1(fs, path, project.envProjectPath));
9334
10107
  const globalEnvText = yield* _(readEnvText(fs, globalEnvPath));
@@ -9339,69 +10112,29 @@ var loadProjectAuthEnvText = (project) => Effect.gen(function* (_) {
9339
10112
  globalEnvPath,
9340
10113
  projectEnvPath: project.envProjectPath,
9341
10114
  claudeAuthPath,
10115
+ geminiAuthPath,
9342
10116
  globalEnvText,
9343
10117
  projectEnvText
9344
10118
  };
9345
10119
  });
9346
- 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 }) => ({
9347
10121
  projectDir: project.projectDir,
9348
10122
  projectName: project.displayName,
9349
10123
  envGlobalPath: globalEnvPath,
9350
10124
  envProjectPath: projectEnvPath,
9351
10125
  claudeAuthPath,
10126
+ geminiAuthPath,
9352
10127
  githubTokenEntries: countKeyEntries(globalEnvText, githubTokenBaseKey),
9353
10128
  gitTokenEntries: countKeyEntries(globalEnvText, gitTokenBaseKey),
9354
10129
  claudeAuthEntries,
10130
+ geminiAuthEntries,
9355
10131
  activeGithubLabel: findEnvValue(projectEnvText, projectGithubLabelKey),
9356
10132
  activeGitLabel: findEnvValue(projectEnvText, projectGitLabelKey),
9357
- activeClaudeLabel: findEnvValue(projectEnvText, projectClaudeLabelKey)
10133
+ activeClaudeLabel: findEnvValue(projectEnvText, projectClaudeLabelKey),
10134
+ activeGeminiLabel: findEnvValue(projectEnvText, projectGeminiLabelKey)
9358
10135
  })))));
9359
- var missingSecret = (provider, label, envPath) => new AuthError({ message: `${provider} not connected: label '${label}' not found in ${envPath}` });
9360
- var updateProjectGithubConnect = (spec) => {
9361
- const key = buildLabeledEnvKey(githubTokenBaseKey, spec.rawLabel);
9362
- const token = findEnvValue(spec.globalEnvText, key);
9363
- if (token === null) return Effect.fail(missingSecret("GitHub token", spec.canonicalLabel, spec.globalEnvPath));
9364
- const withoutGitLabel = upsertEnvKey(upsertEnvKey(upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", token), "GH_TOKEN", token), projectGitLabelKey, "");
9365
- return Effect.succeed(upsertEnvKey(withoutGitLabel, projectGithubLabelKey, spec.canonicalLabel));
9366
- };
9367
- var clearProjectGitLabels = (envText) => {
9368
- return upsertEnvKey(upsertEnvKey(upsertEnvKey(envText, "GH_TOKEN", ""), projectGitLabelKey, ""), projectGithubLabelKey, "");
9369
- };
9370
- var updateProjectGithubDisconnect = (spec) => {
9371
- const withoutGitToken = upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", "");
9372
- return Effect.succeed(clearProjectGitLabels(withoutGitToken));
9373
- };
9374
- var updateProjectGitConnect = (spec) => {
9375
- const tokenKey = buildLabeledEnvKey(gitTokenBaseKey, spec.rawLabel);
9376
- const userKey = buildLabeledEnvKey(gitUserBaseKey, spec.rawLabel);
9377
- const token = findEnvValue(spec.globalEnvText, tokenKey);
9378
- if (token === null) return Effect.fail(missingSecret("Git credentials", spec.canonicalLabel, spec.globalEnvPath));
9379
- const defaultUser = findEnvValue(spec.globalEnvText, gitUserBaseKey) ?? defaultGitUser;
9380
- const user = findEnvValue(spec.globalEnvText, userKey) ?? defaultUser;
9381
- const withGitLabel = upsertEnvKey(upsertEnvKey(upsertEnvKey(upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", token), "GIT_AUTH_USER", user), "GH_TOKEN", token), projectGitLabelKey, spec.canonicalLabel);
9382
- return Effect.succeed(upsertEnvKey(withGitLabel, projectGithubLabelKey, spec.canonicalLabel));
9383
- };
9384
- var updateProjectGitDisconnect = (spec) => {
9385
- const withoutUser = upsertEnvKey(upsertEnvKey(spec.projectEnvText, "GIT_AUTH_TOKEN", ""), "GIT_AUTH_USER", "");
9386
- return Effect.succeed(clearProjectGitLabels(withoutUser));
9387
- };
9388
- var resolveClaudeAccountCandidates = (claudeAuthPath, accountLabel) => accountLabel === "default" ? [`${claudeAuthPath}/default`, claudeAuthPath] : [`${claudeAuthPath}/${accountLabel}`];
9389
- var updateProjectClaudeConnect = (spec) => {
9390
- const accountLabel = normalizeAccountLabel(spec.rawLabel, "default");
9391
- const accountCandidates = resolveClaudeAccountCandidates(spec.claudeAuthPath, accountLabel);
9392
- return Effect.gen(function* (_) {
9393
- for (const accountPath of accountCandidates) {
9394
- if (!(yield* _(spec.fs.exists(accountPath)))) continue;
9395
- if (yield* _(hasClaudeAccountCredentials(spec.fs, accountPath), Effect.orElseSucceed(() => false))) return upsertEnvKey(spec.projectEnvText, projectClaudeLabelKey, spec.canonicalLabel);
9396
- }
9397
- return yield* _(Effect.fail(missingSecret("Claude Code login", spec.canonicalLabel, spec.claudeAuthPath)));
9398
- });
9399
- };
9400
- var updateProjectClaudeDisconnect = (spec) => {
9401
- return Effect.succeed(upsertEnvKey(spec.projectEnvText, projectClaudeLabelKey, ""));
9402
- };
9403
- 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);
9404
- 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 }) => {
9405
10138
  const rawLabel = values["label"] ?? "";
9406
10139
  const canonicalLabel = resolveCanonicalLabel(rawLabel);
9407
10140
  const nextProjectEnv = resolveProjectEnvUpdate(flow, {
@@ -9411,9 +10144,10 @@ var writeProjectAuthFlow = (project, flow, values) => pipe(loadProjectAuthEnvTex
9411
10144
  globalEnvPath,
9412
10145
  globalEnvText,
9413
10146
  projectEnvText,
9414
- claudeAuthPath
10147
+ claudeAuthPath,
10148
+ geminiAuthPath
9415
10149
  });
9416
- 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);
9417
10151
  return pipe(nextProjectEnv, Effect.flatMap((nextText) => fs.writeFileString(projectEnvPath, nextText)), Effect.zipRight(autoSyncState(syncMessage)));
9418
10152
  }), Effect.asVoid);
9419
10153
  var projectAuthViewSteps = (flow) => flowSteps[flow];
@@ -9449,7 +10183,7 @@ var startProjectAuthPrompt = (project, snapshot, flow, context) => {
9449
10183
  var loadProjectAuthMenuView = (project, context) => pipe(readProjectAuthSnapshot(project), Effect.tap((snapshot) => Effect.sync(() => {
9450
10184
  startProjectAuthMenu(project, snapshot, context);
9451
10185
  })), Effect.asVoid);
9452
- 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);
9453
10187
  var runProjectAuthEffect = (project, flow, values, label, context) => {
9454
10188
  context.runner.runEffect(pipe(writeProjectAuthFlow(project, flow, values), Effect.zipRight(readProjectAuthSnapshot(project)), Effect.tap((snapshot) => Effect.sync(() => {
9455
10189
  startProjectAuthMenu(project, snapshot, context);
@@ -9474,7 +10208,7 @@ var runProjectAuthAction$1 = (action, view, context) => {
9474
10208
  context.runner.runEffect(loadProjectAuthMenuView(view.project, context));
9475
10209
  return;
9476
10210
  }
9477
- if (action === "ProjectGithubDisconnect" || action === "ProjectGitDisconnect" || action === "ProjectClaudeDisconnect") {
10211
+ if (action === "ProjectGithubDisconnect" || action === "ProjectGitDisconnect" || action === "ProjectClaudeDisconnect" || action === "ProjectGeminiDisconnect") {
9478
10212
  runProjectAuthEffect(view.project, action, {}, "default", context);
9479
10213
  return;
9480
10214
  }
@@ -10644,7 +11378,7 @@ var setExitCode = (code) => Effect.sync(() => {
10644
11378
  });
10645
11379
  var logWarningAndExit = (error) => pipe(Effect.logWarning(renderError(error)), Effect.tap(() => setExitCode(1)), Effect.asVoid);
10646
11380
  var logErrorAndExit = (error) => pipe(Effect.logError(renderError(error)), Effect.tap(() => setExitCode(1)), Effect.asVoid);
10647
- 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);
10648
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({
10649
11383
  onFailure: (error) => isParseError(error) ? logErrorAndExit(error) : pipe(Effect.logError(renderError(error)), Effect.flatMap(() => Effect.fail(error))),
10650
11384
  onSuccess: () => Effect.void