Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
36199a7
feat: gemini module
35C4n0r Jul 21, 2025
feb7051
feat: add README.md
35C4n0r Jul 21, 2025
1968f9c
feat: bun fmt
35C4n0r Jul 21, 2025
a767299
feat: update tests
35C4n0r Jul 21, 2025
e81c651
feat: gemini api key
35C4n0r Jul 21, 2025
6f9d65c
Merge branch 'main' into feat-gemini-cli
35C4n0r Jul 21, 2025
21f06a4
feat: update README.md
35C4n0r Jul 21, 2025
6d000e4
feat: add support for reporting tasks
35C4n0r Jul 23, 2025
89b1da1
Merge branch 'main' into feat-gemini-cli
35C4n0r Jul 23, 2025
2be7739
Update registry/anomaly/modules/gemini/README.md
35C4n0r Jul 23, 2025
528907e
feat: enhance README and start script for improved task reporting
35C4n0r Jul 23, 2025
086597d
Merge branch 'main' into feat-gemini-cli
35C4n0r Jul 23, 2025
88696ad
fmt: bun fmt
35C4n0r Jul 23, 2025
164083a
fea: update readme to IMPORTANT
35C4n0r Jul 24, 2025
90485f8
Update registry/anomaly/modules/gemini/scripts/start.sh
35C4n0r Jul 24, 2025
4409901
Update registry/anomaly/modules/gemini/main.tf
35C4n0r Jul 24, 2025
b95a68e
Update registry/anomaly/modules/gemini/main.tf
35C4n0r Jul 24, 2025
c8d0a37
refactor: rename variables for clarity in main.tf
35C4n0r Jul 24, 2025
9f06c1c
fix: update default folder path for Gemini configuration
35C4n0r Jul 24, 2025
c9fbada
fix: use HOME variable for user-specific settings path in install.sh
35C4n0r Jul 24, 2025
6e64e43
fix: fix README.md
35C4n0r Jul 24, 2025
b444aaf
feat: update tests
35C4n0r Jul 24, 2025
8d33768
Merge branch 'main' into feat-gemini-cli
DevelopmentCats Jul 25, 2025
02504ef
feat: fix image format
35C4n0r Jul 25, 2025
353c4b9
feat: fix image format
35C4n0r Jul 25, 2025
880137c
fix: correct formatting in tmux README.md
35C4n0r Jul 25, 2025
db20aa5
feat: move gemini module to coder-labs
35C4n0r Jul 25, 2025
0ac5440
Update README.md
35C4n0r Jul 28, 2025
289b84e
Revert "fix: correct formatting in tmux README.md"
35C4n0r Jul 28, 2025
9e14d55
Revert "feat: fix image format"
35C4n0r Jul 28, 2025
f4fe7c4
Revert "feat: fix image format"
35C4n0r Jul 28, 2025
dc39170
feat: b64encode inputs to script
35C4n0r Jul 28, 2025
919eb38
feat: remove comment
35C4n0r Jul 28, 2025
954b713
Merge branch 'main' into feat-gemini-cli
35C4n0r Jul 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .icons/gemini.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 69 additions & 0 deletions registry/anomaly/modules/gemini/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
display_name: Gemini CLI
icon: ../../../../.icons/gemini.svg
description: Run Gemini CLI in your workspace with AgentAPI integration
verified: false
tags: [agent, gemini, ai]
---

# Gemini CLI

Run [Gemini CLI](https://ai.google.dev/gemini-api/docs/cli) in your workspace to access Google's Gemini AI models, and custom pre/post install scripts. This module integrates with [AgentAPI](https://github.com/coder/agentapi) for Coder Tasks compatibility.

```tf
module "gemini" {
source = "registry.coder.com/anomaly/gemini/anomaly"
version = "1.0.0"
agent_id = coder_agent.example.id
gemini_api_key = var.gemini_api_key
gemini_model = "gemini-1.5-pro-latest"
install_gemini = true
gemini_version = "latest"
agentapi_version = "latest"
}
```

## Prerequisites

- You must add the [Coder Login](https://registry.coder.com/modules/coder-login/coder) module to your template
- Node.js and npm will be installed automatically if not present

## Usage Example

```tf
variable "gemini_api_key" {
type = string
description = "Gemini API key"
sensitive = true
}

module "gemini" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/anomaly/gemini/anomaly"
version = "1.0.0"
agent_id = coder_agent.example.id
gemini_api_key = var.gemini_api_key # we recommend providing this parameter inorder to have a smoother experience (i.e. no google sign-in)
gemini_model = "gemini-1.5-pro-latest"
install_gemini = true
gemini_version = "latest"
}
```

## How it Works

- **Install**: The module installs Gemini CLI using npm (installs Node.js if needed)
- **Configure**: Optionally writes your settings JSON to `~/.gemini/settings.json`
- **Start**: Launches Gemini CLI in the specified directory, wrapped by AgentAPI
- **Environment**: Sets `GEMINI_API_KEY`, `GOOGLE_GENAI_USE_VERTEXAI`, `GEMINI_MODEL` for the CLI (if variables provided)

## Troubleshooting

- If Gemini CLI is not found, ensure `install_gemini = true` and your API key is valid
- Node.js and npm are installed automatically if missing
- Check logs in `/home/coder/.gemini-module/` for install/start output

## References

- [Gemini CLI Documentation](https://ai.google.dev/gemini-api/docs/cli)
- [AgentAPI Documentation](https://github.com/coder/agentapi)
- [Coder AI Agents Guide](https://coder.com/docs/tutorials/ai-agents)
35 changes: 35 additions & 0 deletions registry/anomaly/modules/gemini/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { describe, it, expect } from "bun:test";
import {
runTerraformApply,
runTerraformInit,
testRequiredVariables,
findResourceInstance,
} from "~test";
import path from "path";

const moduleDir = path.resolve(__dirname);

const requiredVars = {
agent_id: "dummy-agent-id",
};

describe("gemini module", async () => {
await runTerraformInit(moduleDir);

// 1. Required variables
testRequiredVariables(moduleDir, requiredVars);

// 2. coder_script resource is created
it("creates coder_script resource", async () => {
const state = await runTerraformApply(moduleDir, requiredVars);
const scriptResource = findResourceInstance(state, "coder_script");
expect(scriptResource).toBeDefined();
expect(scriptResource.agent_id).toBe(requiredVars.agent_id);

// check that the script contains expected components based on actual content
expect(scriptResource.script).toContain("ARG_MODULE_DIR_NAME='.gemini-module'");
expect(scriptResource.script).toContain("ARG_INSTALL_AGENTAPI='true'");
expect(scriptResource.script).toContain("ARG_AGENTAPI_VERSION='v0.2.3'");
expect(scriptResource.script).toContain("/tmp/main.sh");
});
});
156 changes: 156 additions & 0 deletions registry/anomaly/modules/gemini/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
terraform {
required_version = ">= 1.0"

required_providers {
coder = {
source = "coder/coder"
version = ">= 2.7"
}
}
}

variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}

data "coder_workspace" "me" {}

data "coder_workspace_owner" "me" {}

variable "order" {
type = number
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
default = null
}

variable "group" {
type = string
description = "The name of a group that this app belongs to."
default = null
}

variable "icon" {
type = string
description = "The icon to use for the app."
default = "/icon/gemini.svg"
}

variable "folder" {
type = string
description = "The folder to run Gemini in."
default = "/home/coder/gemini"
}

variable "install_gemini" {
type = bool
description = "Whether to install Gemini."
default = true
}

variable "gemini_version" {
type = string
description = "The version of Gemini to install."
default = ""
}

variable "gemini_settings_json" {
type = string
description = "json to use in ~/.gemini/settings.json."
default = ""
}

variable "gemini_api_key" {
type = string
description = "Gemini API Key"
default = ""
}

variable "google_genai_use_vertexai" {
type = bool
description = "Whether to use vertex ai"
default = false
}

variable "install_agentapi" {
type = bool
description = "Whether to install AgentAPI."
default = true
}

variable "agentapi_version" {
type = string
description = "The version of AgentAPI to install."
default = "v0.2.3"
}

variable "gemini_model" {
type = string
description = "The model to use for Gemini (e.g., claude-3-5-sonnet-latest)."
default = ""
}

variable "pre_install_script" {
type = string
description = "Custom script to run before installing Gemini."
default = null
}

variable "post_install_script" {
type = string
description = "Custom script to run after installing Gemini."
default = null
}


locals {
app_slug = "gemini"
install_script = file("${path.module}/scripts/install.sh")
start_script = file("${path.module}/scripts/start.sh")
module_dir_name = ".gemini-module"
}

module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "1.0.0"

agent_id = var.agent_id
web_app_slug = local.app_slug
web_app_order = var.order
web_app_group = var.group
web_app_icon = var.icon
web_app_display_name = "Gemini"
cli_app_slug = "${local.app_slug}-cli"
cli_app_display_name = "Gemini CLI"
module_dir_name = local.module_dir_name
install_agentapi = var.install_agentapi
agentapi_version = var.agentapi_version
pre_install_script = var.pre_install_script
post_install_script = var.post_install_script
start_script = <<-EOT
#!/bin/bash
set -o errexit
set -o pipefail

echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh
chmod +x /tmp/start.sh
GEMINI_API_KEY='${var.gemini_api_key}' \
GOOGLE_GENAI_USE_VERTEXAI='${var.google_genai_use_vertexai}' \
GEMINI_MODEL='${var.gemini_model}' \
GEMINI_START_DIRECTORY='${var.folder}' \
/tmp/start.sh
EOT

install_script = <<-EOT
#!/bin/bash
set -o errexit
set -o pipefail

echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh
chmod +x /tmp/install.sh
ARG_INSTALL='${var.install_gemini}' \
ARG_GEMINI_VERSION='${var.gemini_version}' \
ARG_GEMINI_CONFIG='${var.gemini_settings_json}' \
/tmp/install.sh
EOT
}
74 changes: 74 additions & 0 deletions registry/anomaly/modules/gemini/scripts/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/bin/bash

BOLD='\033[0;1m'

# Function to check if a command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}

set -o nounset

echo "--------------------------------"
printf "gemini_config: %s\n" "$ARG_GEMINI_CONFIG\n"
printf "install: %s\n" "$ARG_INSTALL\n"
printf "gemini_version: %s\n" "$ARG_GEMINI_VERSION\n"
echo "--------------------------------"

set +o nounset

function install_node() {
# borrowed from claude-code module
if ! command_exists npm; then
printf "npm not found, checking for Node.js installation...\n"
if ! command_exists node; then
printf "Node.js not found, installing Node.js via NVM...\n"
export NVM_DIR="$HOME/.nvm"
if [ ! -d "$NVM_DIR" ]; then
mkdir -p "$NVM_DIR"
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
else
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
fi

nvm install --lts
nvm use --lts
nvm alias default node

printf "Node.js installed: %s\n" "$(node --version)\n"
printf "npm installed: %s\n" "$(npm --version)\n"
else
printf "Node.js is installed but npm is not available. Please install npm manually.\n"
exit 1
fi
fi
}

function install_gemini() {
if [ "${ARG_INSTALL}" = "true" ]; then
# we need node to install and run gemini-cli
install_node

printf "%s Installing Gemini CLI\n" "$${BOLD}"
if [ -n "$ARG_GEMINI_VERSION" ]; then
npm install -g "@google/gemini-cli@$ARG_GEMINI_VERSION"
else
npm install -g "@google/gemini-cli"
fi
printf "%s Successfully installed Gemini CLI. Version: %s" "$${BOLD}" "$(gemini --version)\n"
fi
}

function populate_settings_json() {
if [ "${ARG_GEMINI_CONFIG}" != "" ]; then
echo "${ARG_GEMINI_CONFIG}" > "/home/coder/.gemini/settings.json"
fi
}



# Install Gemini
install_gemini
populate_settings_json

38 changes: 38 additions & 0 deletions registry/anomaly/modules/gemini/scripts/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/bash

# Load shell environment
source ~/.bashrc
source ~/.nvm/nvm.sh

command_exists() {
command -v "$1" >/dev/null 2>&1
}

printf "Version: %s\n" "$(gemini --version)\n"

if command_exists gemini; then
printf "Gemini is installed\n"
else
printf "Error: Gemini is not installed. Please enable install_gemini or install it manually :)\n"
exit 1
fi

if [ -d "${GEMINI_START_DIRECTORY}" ]; then
printf "Directory '%s' exists. Changing to it.\\n" "${GEMINI_START_DIRECTORY}"
cd "${GEMINI_START_DIRECTORY}" || {
printf "Error: Could not change to directory '%s'.\\n" "${GEMINI_START_DIRECTORY}"
exit 1
}
else
printf "Directory '%s' does not exist. Creating and changing to it.\\n" "${GEMINI_START_DIRECTORY}"
mkdir -p "${GEMINI_START_DIRECTORY}" || {
printf "Error: Could not create directory '%s'.\\n" "${GEMINI_START_DIRECTORY}"
exit 1
}
cd "${GEMINI_START_DIRECTORY}" || {
printf "Error: Could not change to directory '%s'.\\n" "${GEMINI_START_DIRECTORY}"
exit 1
}
fi

agentapi server --term-width 67 --term-height 1190 -- gemini
Loading