Skip to content

Commit 167386b

Browse files
committed
-Create gemini-cli devcontainer feature
- Add Claude Code and Gemini CLI to VSCode, RStudio, JupyterLab, NeMo Jupyter and Parabricks Jupyter - Install AI extensions (Claude Code, Gemini) in VSCode app Fix shellcheck lint errors in gemini-cli and install-extensions
1 parent bf7a78e commit 167386b

13 files changed

Lines changed: 184 additions & 2 deletions

File tree

features/src/gemini-cli/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
# Gemini CLI (gemini-cli)
3+
4+
Installs the Gemini CLI globally
5+
6+
## Example Usage
7+
8+
```json
9+
"features": {
10+
"./.devcontainer/features/gemini-cli": {}
11+
}
12+
```
13+
14+
## Options
15+
16+
| Options Id | Description | Type | Default Value |
17+
|-----|-----|-----|-----|
18+
| username | Username of the container user | string | root |
19+
20+
21+
22+
---
23+
24+
_Note: This file was auto-generated from the [devcontainer-feature.json](devcontainer-feature.json). Add additional notes to a `NOTES.md`._
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "Gemini CLI",
3+
"id": "gemini-cli",
4+
"version": "1.0.0",
5+
"description": "Installs the Gemini CLI globally",
6+
"documentationURL": "https://github.com/google-gemini/gemini-cli",
7+
"options": {
8+
"username": {
9+
"type": "string",
10+
"default": "root",
11+
"description": "Username of the container user"
12+
}
13+
},
14+
"installsAfter": [
15+
"ghcr.io/devcontainers/features/node"
16+
]
17+
}

features/src/gemini-cli/install.sh

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env bash
2+
set -eu
3+
4+
# Function to install Gemini CLI
5+
install_gemini_cli() {
6+
echo "Installing Gemini CLI..."
7+
npm install -g @google/gemini-cli
8+
9+
if command -v gemini >/dev/null; then
10+
echo "Gemini CLI installed successfully!"
11+
return 0
12+
else
13+
echo "ERROR: Gemini CLI installation failed!"
14+
return 1
15+
fi
16+
}
17+
18+
# Function to fix permissions for non-root users
19+
fix_permissions() {
20+
local username="${1:-root}"
21+
22+
if [ "${username}" = "root" ]; then
23+
return 0
24+
fi
25+
26+
# Fix NVM permissions: node feature installs as root, causing "Permission denied" in non-root containers
27+
local nvm_dir="${NVM_DIR:-/usr/local/share/nvm}"
28+
if [ -d "${nvm_dir}" ]; then
29+
echo "Fixing NVM permissions for user ${username}..."
30+
chown -R "${username}:" "${nvm_dir}"
31+
fi
32+
33+
# Fix npm cache: npm install -g as root creates root-owned files in user's ~/.npm
34+
local user_home
35+
user_home=$(eval echo "~${username}" 2>/dev/null || echo "/home/${username}")
36+
if [ -d "${user_home}/.npm" ]; then
37+
echo "Fixing npm cache ownership for user ${username}..."
38+
chown -R "${username}:" "${user_home}/.npm"
39+
fi
40+
41+
# Edge case: Disable auto-update to prevent gemini from trying to re-exec
42+
# itself on first run, which fails on freshly provisioned machines.
43+
mkdir -p "${user_home}/.gemini"
44+
printf '{"general.enableAutoUpdate": false}\n' > "${user_home}/.gemini/settings.json"
45+
chown -R "${username}:" "${user_home}/.gemini"
46+
}
47+
48+
# Print error message about requiring Node.js feature
49+
print_nodejs_requirement() {
50+
cat <<EOF
51+
52+
ERROR: Node.js and npm are required but not found!
53+
Please add the Node.js feature to your devcontainer.json:
54+
55+
"features": {
56+
"ghcr.io/devcontainers/features/node:1": {},
57+
"./.devcontainer/features/gemini-cli": { "username": "your-user" }
58+
}
59+
60+
EOF
61+
exit 1
62+
}
63+
64+
echo "Activating feature 'gemini-cli'"
65+
66+
if ! command -v node >/dev/null || ! command -v npm >/dev/null; then
67+
print_nodejs_requirement
68+
fi
69+
70+
install_gemini_cli || exit 1
71+
72+
fix_permissions "${USERNAME:-root}"
73+
74+
echo "Done!"

features/src/workbench-tools/install.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,18 @@ chown -R "${USERNAME}:" "${LIBRARIES_ENV_DIR}"
180180

181181
# Set CROMWELL_JAR environment variable
182182
printf 'export CROMWELL_JAR="%s"\n' "${BINARIES_ENV_DIR}/share/cromwell/cromwell.jar"
183+
184+
# Wrap gcloud to unset DISPLAY per-invocation so auth commands don't try
185+
# to open a browser via X11 in headless devcontainer environments.
186+
printf 'function gcloud() { DISPLAY= command gcloud "$@"; }\n'
187+
188+
# Wrap claude to clear BROWSER per-invocation so any app-managed browser
189+
# handler does not intercept the auth URL in headless environments.
190+
printf 'function claude() { BROWSER= command claude "$@"; }\n'
191+
192+
# Wrap gemini to set NO_BROWSER=1 per-invocation to force device code flow
193+
# instead of opening a browser window.
194+
printf 'function gemini() { NO_BROWSER=1 NO_COLOR=1 command gemini "$@"; }\n'
183195
} >> "${USER_HOME_DIR}/.bashrc"
184196

185197
# Allow .bashrc to be sourced in non-interactive shells

src/custom-workbench-jupyter-template/.devcontainer.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
"${templateOption:login}"
2222
],
2323
"features": {
24+
"ghcr.io/devcontainers/features/node": {
25+
"version": "24.11.0"
26+
},
27+
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {},
28+
"./.devcontainer/features/gemini-cli": { "username": "jupyter" },
2429
"./.devcontainer/features/workbench-tools": {
2530
"libEnv": "/opt/conda/envs/jupyter", // Use the jupyter conda environment
2631
"cloud": "${templateOption:cloud}",

src/custom-workbench-jupyter-template/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM us-west2-docker.pkg.dev/shared-pub-buckets-94mvrf/workbench-artifacts/app-workbench-jupyter@sha256:229166b2be902aef0a2a621fae38037fe0e069875df1b8bfd5bcff8766d6036c
1+
FROM us-west2-docker.pkg.dev/shared-pub-buckets-94mvrf/workbench-artifacts/app-workbench-jupyter@sha256:229166b2be902aef0a2a621fae38037fe0e069875df1b8bfd5bcff8766d6036c
22

33
# Install jupyter extensions
44
RUN --mount=type=bind,from=jupyter-extension-builder,source=/dist,target=/tmp/extensions \

src/nemo_jupyter/.devcontainer.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
"version": "17"
2525
},
2626
"ghcr.io/dhoeric/features/google-cloud-cli@sha256:fa5d894718825c5ad8009ac8f2c9f0cea3d1661eb108a9d465cba9f3fc48965f": {},
27+
"ghcr.io/devcontainers/features/node": {
28+
"version": "24.11.0"
29+
},
30+
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {},
31+
"./.devcontainer/features/gemini-cli": { "username": "jupyter" },
2732
"./.devcontainer/features/workbench-tools": {
2833
"libPythonVersion": "3.12", // Must match python version in nemo image
2934
"cloud": "${templateOption:cloud}",

src/r-analysis/.devcontainer.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
},
3030
"ghcr.io/devcontainers/features/aws-cli@sha256:17cb4a40151f59144b46957b9264683663b0214371a041ecd53dccc015a4b923": {},
3131
"ghcr.io/dhoeric/features/google-cloud-cli@sha256:fa5d894718825c5ad8009ac8f2c9f0cea3d1661eb108a9d465cba9f3fc48965f": {},
32+
"ghcr.io/devcontainers/features/node": {
33+
"version": "24.11.0"
34+
},
35+
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {},
36+
"./.devcontainer/features/gemini-cli": { "username": "rstudio" },
3237
"./.devcontainer/features/workbench-tools": {
3338
"cloud": "${templateOption:cloud}",
3439
"username": "rstudio",

src/vscode/.devcontainer.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@
1818
"ghcr.io/devcontainers/features/java@sha256:e75d274ac969b29a59ba6f34c2d098f6a52144d0ec027ef326b724ea4b8b7b4e": {
1919
"version": "17"
2020
},
21+
"ghcr.io/devcontainers/features/node": {
22+
"version": "24.11.0"
23+
},
2124
"ghcr.io/devcontainers/features/aws-cli@sha256:17cb4a40151f59144b46957b9264683663b0214371a041ecd53dccc015a4b923": {},
2225
"ghcr.io/dhoeric/features/google-cloud-cli@sha256:fa5d894718825c5ad8009ac8f2c9f0cea3d1661eb108a9d465cba9f3fc48965f": {},
26+
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {},
27+
"./.devcontainer/features/gemini-cli": { "username": "abc" },
2328
"./.devcontainer/features/workbench-tools": {
2429
"cloud": "${templateOption:cloud}",
2530
"username": "abc",

src/vscode/Dockerfile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FROM lscr.io/linuxserver/code-server:4.100.3
2+
3+
# Gemini: https://open-vsx.org/extension/Google/geminicodeassist
4+
# Claude: https://open-vsx.org/extension/Anthropic/claude-code
5+
RUN GEMINI_VERSION=$(curl -fsSL "https://open-vsx.org/api/Google/geminicodeassist/latest" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4) && \
6+
curl -fL --compressed \
7+
"https://open-vsx.org/api/Google/geminicodeassist/${GEMINI_VERSION}/file/Google.geminicodeassist-${GEMINI_VERSION}.vsix" \
8+
-o /opt/geminicodeassist.vsix && \
9+
CLAUDE_VERSION=$(curl -fsSL "https://open-vsx.org/api/Anthropic/claude-code/latest" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4) && \
10+
curl -fL --compressed \
11+
"https://open-vsx.org/api/Anthropic/claude-code/${CLAUDE_VERSION}/file/Anthropic.claude-code-${CLAUDE_VERSION}.vsix" \
12+
-o /opt/claudecode.vsix
13+
14+
# Install extensions during container init, before code-server starts
15+
COPY install-extensions.sh /etc/cont-init.d/99-install-extensions
16+
RUN chmod +x /etc/cont-init.d/99-install-extensions
17+
18+
WORKDIR /config

0 commit comments

Comments
 (0)