-
Notifications
You must be signed in to change notification settings - Fork 31
Add Claude Code and Gemini CLI to Workbench apps #342
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
|
|
||
| # Gemini CLI (gemini-cli) | ||
|
|
||
| Installs the Gemini CLI globally | ||
|
|
||
| ## Example Usage | ||
|
|
||
| ```json | ||
| "features": { | ||
| "./.devcontainer/features/gemini-cli": {} | ||
| } | ||
| ``` | ||
|
|
||
| ## Options | ||
|
|
||
| | Options Id | Description | Type | Default Value | | ||
| |-----|-----|-----|-----| | ||
| | username | Username of the container user | string | root | | ||
|
|
||
|
|
||
|
|
||
| --- | ||
|
|
||
| _Note: This file was auto-generated from the [devcontainer-feature.json](devcontainer-feature.json). Add additional notes to a `NOTES.md`._ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| { | ||
| "name": "Gemini CLI", | ||
| "id": "gemini-cli", | ||
| "version": "1.0.0", | ||
| "description": "Installs the Gemini CLI globally", | ||
| "documentationURL": "https://github.com/google-gemini/gemini-cli", | ||
| "options": { | ||
| "username": { | ||
| "type": "string", | ||
| "default": "root", | ||
| "description": "Username of the container user" | ||
| } | ||
| }, | ||
| "installsAfter": [ | ||
| "ghcr.io/devcontainers/features/node" | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| #!/usr/bin/env bash | ||
| set -eu | ||
|
|
||
| # Function to install Gemini CLI | ||
| install_gemini_cli() { | ||
| echo "Installing Gemini CLI..." | ||
| npm install -g @google/gemini-cli | ||
|
|
||
| if command -v gemini >/dev/null; then | ||
| echo "Gemini CLI installed successfully!" | ||
| return 0 | ||
| else | ||
| echo "ERROR: Gemini CLI installation failed!" | ||
| return 1 | ||
| fi | ||
| } | ||
|
|
||
| # Function to fix permissions for non-root users | ||
| fix_permissions() { | ||
| local username="${1:-root}" | ||
|
|
||
| if [ "${username}" = "root" ]; then | ||
| return 0 | ||
| fi | ||
|
|
||
| # Fix NVM permissions: node feature installs as root, causing "Permission denied" in non-root containers | ||
| local nvm_dir="${NVM_DIR:-/usr/local/share/nvm}" | ||
| if [ -d "${nvm_dir}" ]; then | ||
| echo "Fixing NVM permissions for user ${username}..." | ||
| chown -R "${username}:" "${nvm_dir}" | ||
| fi | ||
|
|
||
| # Fix npm cache: npm install -g as root creates root-owned files in user's ~/.npm | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if you installed it as the user? |
||
| local user_home | ||
| user_home=$(eval echo "~${username}" 2>/dev/null || echo "/home/${username}") | ||
| if [ -d "${user_home}/.npm" ]; then | ||
| echo "Fixing npm cache ownership for user ${username}..." | ||
| chown -R "${username}:" "${user_home}/.npm" | ||
| fi | ||
|
|
||
| # Edge case: Disable auto-update to prevent gemini from trying to re-exec | ||
| # itself on first run, which fails on freshly provisioned machines. | ||
| mkdir -p "${user_home}/.gemini" | ||
| printf '{"general.enableAutoUpdate": false}\n' > "${user_home}/.gemini/settings.json" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. did this fix that freezing issue you showed me? |
||
| chown -R "${username}:" "${user_home}/.gemini" | ||
| } | ||
|
|
||
| # Print error message about requiring Node.js feature | ||
| print_nodejs_requirement() { | ||
| cat <<EOF | ||
| ERROR: Node.js and npm are required but not found! | ||
| Please add the Node.js feature to your devcontainer.json: | ||
| "features": { | ||
| "ghcr.io/devcontainers/features/node:1": {}, | ||
| "./.devcontainer/features/gemini-cli": { "username": "your-user" } | ||
| } | ||
| EOF | ||
| exit 1 | ||
| } | ||
|
|
||
| echo "Activating feature 'gemini-cli'" | ||
|
|
||
| if ! command -v node >/dev/null || ! command -v npm >/dev/null; then | ||
| print_nodejs_requirement | ||
| fi | ||
|
|
||
| install_gemini_cli || exit 1 | ||
|
|
||
| fix_permissions "${USERNAME:-root}" | ||
|
|
||
| echo "Done!" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -180,6 +180,18 @@ chown -R "${USERNAME}:" "${LIBRARIES_ENV_DIR}" | |
|
|
||
| # Set CROMWELL_JAR environment variable | ||
| printf 'export CROMWELL_JAR="%s"\n' "${BINARIES_ENV_DIR}/share/cromwell/cromwell.jar" | ||
|
|
||
| # Wrap gcloud to unset DISPLAY per-invocation so auth commands don't try | ||
| # to open a browser via X11 in headless devcontainer environments. | ||
| printf 'function gcloud() { DISPLAY= command gcloud "$@"; }\n' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could these env variables be set and exported rather than wrapping the commands? I wouldn't think they would cause issues with other commands? |
||
|
|
||
| # Wrap claude to clear BROWSER per-invocation so any app-managed browser | ||
| # handler does not intercept the auth URL in headless environments. | ||
| printf 'function claude() { BROWSER= command claude "$@"; }\n' | ||
|
|
||
| # Wrap gemini to set NO_BROWSER=1 per-invocation to force device code flow | ||
| # instead of opening a browser window. | ||
| printf 'function gemini() { NO_BROWSER=1 NO_COLOR=1 command gemini "$@"; }\n' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why NO_COLOR? |
||
| } >> "${USER_HOME_DIR}/.bashrc" | ||
|
|
||
| # Allow .bashrc to be sourced in non-interactive shells | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,11 @@ | |
| "${templateOption:login}" | ||
| ], | ||
| "features": { | ||
| "ghcr.io/devcontainers/features/node": { | ||
| "version": "24.11.0" | ||
| }, | ||
| "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}, | ||
|
Comment on lines
+24
to
+27
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we pin these features to a hash and add them to https://github.com/verily-src/workbench-app-devcontainers/blob/master/feature-versions/state.json so they can be auto-updated? |
||
| "./.devcontainer/features/gemini-cli": { "username": "jupyter" }, | ||
| "./.devcontainer/features/workbench-tools": { | ||
| "libEnv": "/opt/conda/envs/jupyter", // Use the jupyter conda environment | ||
| "cloud": "${templateOption:cloud}", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| FROM lscr.io/linuxserver/code-server:4.100.3 | ||
|
|
||
| # Gemini: https://open-vsx.org/extension/Google/geminicodeassist | ||
| # Claude: https://open-vsx.org/extension/Anthropic/claude-code | ||
| RUN GEMINI_VERSION=$(curl -fsSL "https://open-vsx.org/api/Google/geminicodeassist/latest" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4) && \ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we use jq here instead of grep/head/cut? We can even get the full download url with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I recently learned in Dockerfiles it's preferred to have the && curl ... \
... \
-o ... \
&& CLAUDE_VERSION=... |
||
| curl -fL --compressed \ | ||
| "https://open-vsx.org/api/Google/geminicodeassist/${GEMINI_VERSION}/file/Google.geminicodeassist-${GEMINI_VERSION}.vsix" \ | ||
| -o /opt/geminicodeassist.vsix && \ | ||
| CLAUDE_VERSION=$(curl -fsSL "https://open-vsx.org/api/Anthropic/claude-code/latest" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4) && \ | ||
| curl -fL --compressed \ | ||
| "https://open-vsx.org/api/Anthropic/claude-code/${CLAUDE_VERSION}/file/Anthropic.claude-code-${CLAUDE_VERSION}.vsix" \ | ||
| -o /opt/claudecode.vsix | ||
|
|
||
| # Install extensions during container init, before code-server starts | ||
| COPY install-extensions.sh /etc/cont-init.d/99-install-extensions | ||
| RUN chmod +x /etc/cont-init.d/99-install-extensions | ||
|
|
||
| WORKDIR /config | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| #!/usr/bin/with-contenv bash | ||
| # shellcheck shell=bash | ||
| # Installs VS Code extensions once on first container boot. | ||
|
|
||
| [ -f /config/.extensions-installed ] && exit 0 | ||
|
|
||
| HOME=/config s6-setuidgid abc /app/code-server/bin/code-server --extensions-dir /config/extensions --install-extension /opt/geminicodeassist.vsix | ||
| HOME=/config s6-setuidgid abc /app/code-server/bin/code-server --extensions-dir /config/extensions --install-extension /opt/claudecode.vsix | ||
|
|
||
| touch /config/.extensions-installed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
set -o errexit
set -o nounset
set -o pipefail
set -o xtrace