feat: gemini cli module (#246)
Closes #237 /claim #237 ## Description ~https://www.loom.com/share/5b099c73935f4f87b8fdafe1509bb79d?sid=7fea43d6-86e9-45ae-9892-efeb3c820b82~ Updated: https://www.loom.com/share/62e907eae8544d8cbbe560d7f63bd02d?sid=201212fa-eb58-44b5-8706-8bf9c2c37433 <!-- Briefly describe what this PR does and why --> ## Type of Change - [x] New module - [ ] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information <!-- Delete this section if not applicable --> **Path:** `registry/coder-labs/modules/gemini` **New version:** `v1.0.0` **Breaking change:** [ ] Yes [ ] No ## Testing & Validation - [x] Tests pass (`bun test`) - [x] Code formatted (`bun run fmt`) - [x] Changes tested locally ## Related Issues <!-- Link related issues or write "None" if not applicable --> --------- Co-authored-by: Atif Ali <me@matifali.dev> Co-authored-by: Hugo Dutka <dutkahugo@gmail.com> Co-authored-by: DevCats <christofer@coder.com>
This commit is contained in:
parent
0fe47943aa
commit
f89ea12d9e
1
.icons/gemini.svg
Normal file
1
.icons/gemini.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="#3186FF"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-0)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-1)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-fill-2)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-0" x1="7" x2="11" y1="15.5" y2="12"><stop stop-color="#08B962"></stop><stop offset="1" stop-color="#08B962" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-1" x1="8" x2="11.5" y1="5.5" y2="11"><stop stop-color="#F94543"></stop><stop offset="1" stop-color="#F94543" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-fill-2" x1="3.5" x2="17.5" y1="13.5" y2="12"><stop stop-color="#FABC12"></stop><stop offset=".46" stop-color="#FABC12" stop-opacity="0"></stop></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
78
registry/coder-labs/modules/gemini/README.md
Normal file
78
registry/coder-labs/modules/gemini/README.md
Normal file
@ -0,0 +1,78 @@
|
||||
---
|
||||
display_name: Gemini CLI
|
||||
icon: ../../../../.icons/gemini.svg
|
||||
description: Run Gemini CLI in your workspace with AgentAPI integration
|
||||
verified: true
|
||||
tags: [agent, gemini, ai, google, tasks]
|
||||
---
|
||||
|
||||
# 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/coder-labs/gemini/coder"
|
||||
version = "1.0.0"
|
||||
agent_id = coder_agent.example.id
|
||||
gemini_api_key = var.gemini_api_key
|
||||
gemini_model = "gemini-2.5-pro"
|
||||
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
|
||||
|
||||
- Example 1:
|
||||
|
||||
```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/coder-labs/gemini/coder"
|
||||
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-2.5-flash"
|
||||
install_gemini = true
|
||||
gemini_version = "latest"
|
||||
gemini_instruction_prompt = "Start every response with `Gemini says:`"
|
||||
}
|
||||
```
|
||||
|
||||
## How it Works
|
||||
|
||||
- **Install**: The module installs Gemini CLI using npm (installs Node.js via NVM if needed)
|
||||
- **Instruction Prompt**: If `GEMINI_INSTRUCTION_PROMPT` and `GEMINI_START_DIRECTORY` are set, creates the directory (if needed) and writes the prompt to `GEMINI.md`
|
||||
- **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 (using NVM)
|
||||
- Check logs in `/home/coder/.gemini-module/` for install/start output
|
||||
- We highly recommend using the `gemini_api_key` variable, this also ensures smooth tasks running without needing to sign in to Google.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> To use tasks with Gemini CLI, ensure you have the `gemini_api_key` variable set, and **you pass the `AI Prompt` Parameter**.
|
||||
> By default we inject the "theme": "Default" and "selectedAuthType": "gemini-api-key" to your ~/.gemini/settings.json along with the coder mcp server.
|
||||
> In `gemini_instruction_prompt` and `AI Prompt` text we recommend using (\`\`) backticks instead of quotes to avoid escaping issues. Eg: gemini_instruction_prompt = "Start every response with \`Gemini says:\` "
|
||||
|
||||
## 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)
|
||||
207
registry/coder-labs/modules/gemini/main.test.ts
Normal file
207
registry/coder-labs/modules/gemini/main.test.ts
Normal file
@ -0,0 +1,207 @@
|
||||
import {
|
||||
test,
|
||||
afterEach,
|
||||
describe,
|
||||
setDefaultTimeout,
|
||||
beforeAll,
|
||||
expect,
|
||||
} from "bun:test";
|
||||
import { execContainer, readFileContainer, runTerraformInit } from "~test";
|
||||
import {
|
||||
loadTestFile,
|
||||
writeExecutable,
|
||||
setup as setupUtil,
|
||||
execModuleScript,
|
||||
expectAgentAPIStarted,
|
||||
} from "../../../coder/modules/agentapi/test-util";
|
||||
|
||||
let cleanupFunctions: (() => Promise<void>)[] = [];
|
||||
const registerCleanup = (cleanup: () => Promise<void>) => {
|
||||
cleanupFunctions.push(cleanup);
|
||||
};
|
||||
afterEach(async () => {
|
||||
const cleanupFnsCopy = cleanupFunctions.slice().reverse();
|
||||
cleanupFunctions = [];
|
||||
for (const cleanup of cleanupFnsCopy) {
|
||||
try {
|
||||
await cleanup();
|
||||
} catch (error) {
|
||||
console.error("Error during cleanup:", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
interface SetupProps {
|
||||
skipAgentAPIMock?: boolean;
|
||||
skipGeminiMock?: boolean;
|
||||
moduleVariables?: Record<string, string>;
|
||||
agentapiMockScript?: string;
|
||||
}
|
||||
|
||||
const setup = async (props?: SetupProps): Promise<{ id: string }> => {
|
||||
const projectDir = "/home/coder/project";
|
||||
const { id } = await setupUtil({
|
||||
moduleDir: import.meta.dir,
|
||||
moduleVariables: {
|
||||
install_gemini: props?.skipGeminiMock ? "true" : "false",
|
||||
install_agentapi: props?.skipAgentAPIMock ? "true" : "false",
|
||||
gemini_model: "test-model",
|
||||
...props?.moduleVariables,
|
||||
},
|
||||
registerCleanup,
|
||||
projectDir,
|
||||
skipAgentAPIMock: props?.skipAgentAPIMock,
|
||||
agentapiMockScript: props?.agentapiMockScript,
|
||||
});
|
||||
if (!props?.skipGeminiMock) {
|
||||
await writeExecutable({
|
||||
containerId: id,
|
||||
filePath: "/usr/bin/gemini",
|
||||
content: await loadTestFile(import.meta.dir, "gemini-mock.sh"),
|
||||
});
|
||||
}
|
||||
return { id };
|
||||
};
|
||||
|
||||
setDefaultTimeout(60 * 1000);
|
||||
|
||||
describe("gemini", async () => {
|
||||
beforeAll(async () => {
|
||||
await runTerraformInit(import.meta.dir);
|
||||
});
|
||||
|
||||
test("happy-path", async () => {
|
||||
const { id } = await setup();
|
||||
await execModuleScript(id);
|
||||
await expectAgentAPIStarted(id);
|
||||
});
|
||||
|
||||
test("install-gemini-version", async () => {
|
||||
const version_to_install = "0.1.13";
|
||||
const { id } = await setup({
|
||||
skipGeminiMock: true,
|
||||
moduleVariables: {
|
||||
install_gemini: "true",
|
||||
gemini_version: version_to_install,
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
const resp = await execContainer(id, [
|
||||
"bash",
|
||||
"-c",
|
||||
`cat /home/coder/.gemini-module/install.log || true`,
|
||||
]);
|
||||
expect(resp.stdout).toContain(version_to_install);
|
||||
});
|
||||
|
||||
test("gemini-settings-json", async () => {
|
||||
const settings = '{"foo": "bar"}';
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
gemini_settings_json: settings,
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
const resp = await readFileContainer(id, "/home/coder/.gemini/settings.json");
|
||||
expect(resp).toContain("foo");
|
||||
expect(resp).toContain("bar");
|
||||
});
|
||||
|
||||
test("gemini-api-key", async () => {
|
||||
const apiKey = "test-api-key-123";
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
gemini_api_key: apiKey,
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
|
||||
const resp = await readFileContainer(id, "/home/coder/.gemini-module/agentapi-start.log");
|
||||
expect(resp).toContain("gemini_api_key provided !");
|
||||
});
|
||||
|
||||
test("use-vertexai", async () => {
|
||||
const { id } = await setup({
|
||||
skipGeminiMock: false,
|
||||
moduleVariables: {
|
||||
use_vertexai: "true",
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
const resp = await readFileContainer(id, "/home/coder/.gemini-module/install.log");
|
||||
expect(resp).toContain('GOOGLE_GENAI_USE_VERTEXAI=\'true\'');
|
||||
});
|
||||
|
||||
test("gemini-model", async () => {
|
||||
const model = "gemini-2.5-pro";
|
||||
const { id } = await setup({
|
||||
skipGeminiMock: false,
|
||||
moduleVariables: {
|
||||
gemini_model: model,
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
const resp = await readFileContainer(id, "/home/coder/.gemini-module/install.log");
|
||||
expect(resp).toContain(model);
|
||||
});
|
||||
|
||||
test("pre-post-install-scripts", async () => {
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
pre_install_script: "#!/bin/bash\necho 'pre-install-script'",
|
||||
post_install_script: "#!/bin/bash\necho 'post-install-script'",
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
const preInstallLog = await readFileContainer(id, "/home/coder/.gemini-module/pre_install.log");
|
||||
expect(preInstallLog).toContain("pre-install-script");
|
||||
const postInstallLog = await readFileContainer(id, "/home/coder/.gemini-module/post_install.log");
|
||||
expect(postInstallLog).toContain("post-install-script");
|
||||
});
|
||||
|
||||
test("folder-variable", async () => {
|
||||
const folder = "/tmp/gemini-test-folder";
|
||||
const { id } = await setup({
|
||||
skipGeminiMock: false,
|
||||
moduleVariables: {
|
||||
folder,
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
const resp = await readFileContainer(id, "/home/coder/.gemini-module/install.log");
|
||||
expect(resp).toContain(folder);
|
||||
});
|
||||
|
||||
test("additional-extensions", async () => {
|
||||
const additional = '{"custom": {"enabled": true}}';
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
additional_extensions: additional,
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
const resp = await readFileContainer(id, "/home/coder/.gemini/settings.json");
|
||||
expect(resp).toContain("custom");
|
||||
expect(resp).toContain("enabled");
|
||||
});
|
||||
|
||||
test("gemini-system-prompt", async () => {
|
||||
const prompt = "This is a system prompt for Gemini.";
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
gemini_system_prompt: prompt,
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
const resp = await readFileContainer(id, "/home/coder/GEMINI.md");
|
||||
expect(resp).toContain(prompt);
|
||||
});
|
||||
|
||||
test("start-without-prompt", async () => {
|
||||
const { id } = await setup();
|
||||
await execModuleScript(id);
|
||||
const prompt = await execContainer(id, ["ls", "-l", "/home/coder/GEMINI.md"]);
|
||||
expect(prompt.exitCode).not.toBe(0);
|
||||
expect(prompt.stderr).toContain("No such file or directory");
|
||||
});
|
||||
});
|
||||
215
registry/coder-labs/modules/gemini/main.tf
Normal file
215
registry/coder-labs/modules/gemini/main.tf
Normal file
@ -0,0 +1,215 @@
|
||||
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"
|
||||
}
|
||||
|
||||
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 "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.3.0"
|
||||
}
|
||||
|
||||
variable "gemini_model" {
|
||||
type = string
|
||||
description = "The model to use for Gemini (e.g., gemini-2.5-pro)."
|
||||
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
|
||||
}
|
||||
|
||||
data "coder_parameter" "ai_prompt" {
|
||||
type = "string"
|
||||
name = "AI Prompt"
|
||||
default = ""
|
||||
description = "Initial prompt for the Gemini CLI"
|
||||
mutable = true
|
||||
}
|
||||
|
||||
variable "additional_extensions" {
|
||||
type = string
|
||||
description = "Additional extensions configuration in json format to append to the config."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "gemini_system_prompt" {
|
||||
type = string
|
||||
description = "System prompt for Gemini. It will be added to GEMINI.md in the specified folder."
|
||||
default = ""
|
||||
}
|
||||
|
||||
resource "coder_env" "gemini_api_key" {
|
||||
agent_id = var.agent_id
|
||||
name = "GEMINI_API_KEY"
|
||||
value = var.gemini_api_key
|
||||
}
|
||||
|
||||
resource "coder_env" "gemini_use_vertex_ai" {
|
||||
agent_id = var.agent_id
|
||||
name = "GOOGLE_GENAI_USE_VERTEXAI"
|
||||
value = var.use_vertexai
|
||||
}
|
||||
|
||||
locals {
|
||||
base_extensions = <<-EOT
|
||||
{
|
||||
"coder": {
|
||||
"args": [
|
||||
"exp",
|
||||
"mcp",
|
||||
"server"
|
||||
],
|
||||
"command": "coder",
|
||||
"description": "Report ALL tasks and statuses (in progress, done, failed) you are working on.",
|
||||
"enabled": true,
|
||||
"env": {
|
||||
"CODER_MCP_APP_STATUS_SLUG": "${local.app_slug}",
|
||||
"CODER_MCP_AI_AGENTAPI_URL": "http://localhost:3284"
|
||||
},
|
||||
"name": "Coder",
|
||||
"timeout": 3000,
|
||||
"type": "stdio",
|
||||
"trust": true
|
||||
}
|
||||
}
|
||||
EOT
|
||||
|
||||
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.use_vertexai}' \
|
||||
GEMINI_MODEL='${var.gemini_model}' \
|
||||
GEMINI_START_DIRECTORY='${var.folder}' \
|
||||
GEMINI_TASK_PROMPT='${base64encode(data.coder_parameter.ai_prompt.value)}' \
|
||||
/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='${base64encode(var.gemini_settings_json)}' \
|
||||
BASE_EXTENSIONS='${base64encode(replace(local.base_extensions, "'", "'\\''"))}' \
|
||||
ADDITIONAL_EXTENSIONS='${base64encode(replace(var.additional_extensions != null ? var.additional_extensions : "", "'", "'\\''"))}' \
|
||||
GEMINI_START_DIRECTORY='${var.folder}' \
|
||||
GEMINI_INSTRUCTION_PROMPT='${base64encode(var.gemini_system_prompt)}' \
|
||||
/tmp/install.sh
|
||||
EOT
|
||||
}
|
||||
175
registry/coder-labs/modules/gemini/scripts/install.sh
Normal file
175
registry/coder-labs/modules/gemini/scripts/install.sh
Normal file
@ -0,0 +1,175 @@
|
||||
#!/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
|
||||
|
||||
ARG_GEMINI_CONFIG=$(echo -n "$ARG_GEMINI_CONFIG" | base64 -d)
|
||||
BASE_EXTENSIONS=$(echo -n "$BASE_EXTENSIONS" | base64 -d)
|
||||
ADDITIONAL_EXTENSIONS=$(echo -n "$ADDITIONAL_EXTENSIONS" | base64 -d)
|
||||
GEMINI_INSTRUCTION_PROMPT=$(echo -n "$GEMINI_INSTRUCTION_PROMPT" | base64 -d)
|
||||
|
||||
echo "--------------------------------"
|
||||
printf "gemini_config: %s\n" "$ARG_GEMINI_CONFIG"
|
||||
printf "install: %s\n" "$ARG_INSTALL"
|
||||
printf "gemini_version: %s\n" "$ARG_GEMINI_VERSION"
|
||||
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)"
|
||||
printf "npm installed: %s\n" "$(npm --version)"
|
||||
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
|
||||
|
||||
# If nvm does not exist, we will create a global npm directory (this os to prevent the possibility of EACCESS issues on npm -g)
|
||||
if ! command_exists nvm; then
|
||||
printf "which node: %s\n" "$(which node)"
|
||||
printf "which npm: %s\n" "$(which npm)"
|
||||
|
||||
# Create a directory for global packages
|
||||
mkdir -p "$HOME"/.npm-global
|
||||
|
||||
# Configure npm to use it
|
||||
npm config set prefix "$HOME/.npm-global"
|
||||
|
||||
# Add to PATH for current session
|
||||
export PATH="$HOME/.npm-global/bin:$PATH"
|
||||
|
||||
# Add to shell profile for future sessions
|
||||
if ! grep -q "export PATH=$HOME/.npm-global/bin:\$PATH" ~/.bashrc; then
|
||||
echo "export PATH=$HOME/.npm-global/bin:\$PATH" >> ~/.bashrc
|
||||
fi
|
||||
fi
|
||||
|
||||
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\n" "${BOLD}" "$(gemini --version)"
|
||||
fi
|
||||
}
|
||||
|
||||
function populate_settings_json() {
|
||||
if [ "${ARG_GEMINI_CONFIG}" != "" ]; then
|
||||
SETTINGS_PATH="$HOME/.gemini/settings.json"
|
||||
mkdir -p "$(dirname "$SETTINGS_PATH")"
|
||||
printf "Custom gemini_config is provided !\n"
|
||||
echo "${ARG_GEMINI_CONFIG}" > "$HOME/.gemini/settings.json"
|
||||
else
|
||||
printf "No custom gemini_config provided, using default settings.json.\n"
|
||||
append_extensions_to_settings_json
|
||||
fi
|
||||
}
|
||||
|
||||
function append_extensions_to_settings_json() {
|
||||
SETTINGS_PATH="$HOME/.gemini/settings.json"
|
||||
mkdir -p "$(dirname "$SETTINGS_PATH")"
|
||||
printf "[append_extensions_to_settings_json] Starting extension merge process...\n"
|
||||
if [ -z "${BASE_EXTENSIONS:-}" ]; then
|
||||
printf "[append_extensions_to_settings_json] BASE_EXTENSIONS is empty, skipping merge.\n"
|
||||
return
|
||||
fi
|
||||
if [ ! -f "$SETTINGS_PATH" ]; then
|
||||
printf "%s does not exist. Creating with merged mcpServers structure.\n" "$SETTINGS_PATH"
|
||||
# If ADDITIONAL_EXTENSIONS is not set or empty, use '{}'
|
||||
ADD_EXT_JSON='{}'
|
||||
if [ -n "${ADDITIONAL_EXTENSIONS:-}" ]; then
|
||||
ADD_EXT_JSON="$ADDITIONAL_EXTENSIONS"
|
||||
fi
|
||||
printf '{"mcpServers":%s}\n' "$(jq -s 'add' <(echo "$BASE_EXTENSIONS") <(echo "$ADD_EXT_JSON"))" > "$SETTINGS_PATH"
|
||||
fi
|
||||
|
||||
# Prepare temp files
|
||||
TMP_SETTINGS=$(mktemp)
|
||||
|
||||
# If ADDITIONAL_EXTENSIONS is not set or empty, use '{}'
|
||||
ADD_EXT_JSON='{}'
|
||||
if [ -n "${ADDITIONAL_EXTENSIONS:-}" ]; then
|
||||
printf "[append_extensions_to_settings_json] ADDITIONAL_EXTENSIONS is set.\n"
|
||||
ADD_EXT_JSON="$ADDITIONAL_EXTENSIONS"
|
||||
else
|
||||
printf "[append_extensions_to_settings_json] ADDITIONAL_EXTENSIONS is empty or not set.\n"
|
||||
fi
|
||||
|
||||
printf "[append_extensions_to_settings_json] Merging BASE_EXTENSIONS and ADDITIONAL_EXTENSIONS into mcpServers...\n"
|
||||
jq --argjson base "$BASE_EXTENSIONS" --argjson add "$ADD_EXT_JSON" \
|
||||
'.mcpServers = (.mcpServers // {} + $base + $add)' \
|
||||
"$SETTINGS_PATH" > "$TMP_SETTINGS" && mv "$TMP_SETTINGS" "$SETTINGS_PATH"
|
||||
|
||||
# Add theme and selectedAuthType fields
|
||||
jq '.theme = "Default" | .selectedAuthType = "gemini-api-key"' "$SETTINGS_PATH" > "$TMP_SETTINGS" && mv "$TMP_SETTINGS" "$SETTINGS_PATH"
|
||||
|
||||
printf "[append_extensions_to_settings_json] Merge complete.\n"
|
||||
}
|
||||
|
||||
function add_instruction_prompt_if_exists() {
|
||||
if [ -n "${GEMINI_INSTRUCTION_PROMPT:-}" ]; then
|
||||
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
|
||||
touch GEMINI.md
|
||||
printf "Setting GEMINI.md\n"
|
||||
echo "${GEMINI_INSTRUCTION_PROMPT}" > GEMINI.md
|
||||
else
|
||||
printf "GEMINI.md is not set.\n"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# Install Gemini
|
||||
install_gemini
|
||||
gemini --version
|
||||
populate_settings_json
|
||||
add_instruction_prompt_if_exists
|
||||
|
||||
62
registry/coder-labs/modules/gemini/scripts/start.sh
Normal file
62
registry/coder-labs/modules/gemini/scripts/start.sh
Normal file
@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Load shell environment
|
||||
source "$HOME"/.bashrc
|
||||
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
if [ -f "$HOME/.nvm/nvm.sh" ]; then
|
||||
source "$HOME"/.nvm/nvm.sh
|
||||
else
|
||||
export PATH="$HOME/.npm-global/bin:$PATH"
|
||||
fi
|
||||
|
||||
printf "Version: %s\n" "$(gemini --version)"
|
||||
|
||||
GEMINI_TASK_PROMPT=$(echo -n "$GEMINI_TASK_PROMPT" | base64 -d)
|
||||
|
||||
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
|
||||
|
||||
if [ -n "$GEMINI_TASK_PROMPT" ]; then
|
||||
printf "Running the task prompt %s\n" "$GEMINI_TASK_PROMPT"
|
||||
PROMPT="Every step of the way, report tasks to Coder with proper descriptions and statuses. Your task at hand: $GEMINI_TASK_PROMPT"
|
||||
GEMINI_ARGS=(--prompt-interactive "$PROMPT")
|
||||
else
|
||||
printf "No task prompt given.\n"
|
||||
GEMINI_ARGS=()
|
||||
fi
|
||||
|
||||
if [ -n "$GEMINI_API_KEY" ]; then
|
||||
printf "gemini_api_key provided !\n"
|
||||
else
|
||||
printf "gemini_api_key not provided\n"
|
||||
fi
|
||||
|
||||
# use low width to fit in the tasks UI sidebar. height is adjusted so that width x height ~= 80x1000 characters
|
||||
# are visible in the terminal screen by default.
|
||||
agentapi server --term-width 67 --term-height 1190 -- gemini "${GEMINI_ARGS[@]}"
|
||||
14
registry/coder-labs/modules/gemini/testdata/gemini-mock.sh
vendored
Normal file
14
registry/coder-labs/modules/gemini/testdata/gemini-mock.sh
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ "$1" == "--version" ]]; then
|
||||
echo "HELLO: $(bash -c env)"
|
||||
echo "gemini version v2.5.0"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
set -e
|
||||
|
||||
while true; do
|
||||
echo "$(date) - gemini-mock"
|
||||
sleep 15
|
||||
done
|
||||
Loading…
x
Reference in New Issue
Block a user