feat: codex qol updates (#348)

## Description

- Removed variables for hardcoded configuration options, and replaced
with variables for base config, and additional mcp servers.
- Set module defaults so that this will run with minimal module
configuration for tasks, while allowing further configuration if needed
by the user for codex through the base configuration.
- Updated tests for expected responses and new configuration options.
- Move all codex related files outside of project folder.
<!-- Briefly describe what this PR does and why -->

## Type of Change

- [ ] New module
- [ ] Bug fix
- [X] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Module Information

<!-- Delete this section if not applicable -->

**Path:** `registry/coder-labs/modules/codex`  
**New version:** `v1.1.0`  
**Breaking change:** [X] 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 <atif@coder.com>
This commit is contained in:
DevCats 2025-08-21 10:56:09 -05:00 committed by GitHub
parent eca289be3a
commit d6ae51fad0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 250 additions and 214 deletions

View File

@ -13,7 +13,7 @@ Run Codex CLI in your workspace to access OpenAI's models through the Codex inte
```tf ```tf
module "codex" { module "codex" {
source = "registry.coder.com/coder-labs/codex/coder" source = "registry.coder.com/coder-labs/codex/coder"
version = "1.0.1" version = "2.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
openai_api_key = var.openai_api_key openai_api_key = var.openai_api_key
folder = "/home/coder/project" folder = "/home/coder/project"
@ -25,29 +25,24 @@ module "codex" {
- You must add the [Coder Login](https://registry.coder.com/modules/coder/coder-login) module to your template - You must add the [Coder Login](https://registry.coder.com/modules/coder/coder-login) module to your template
- OpenAI API key for Codex access - OpenAI API key for Codex access
## Usage Example ## Examples
- Simple usage Example: ### Run standalone
```tf ```tf
module "codex" { module "codex" {
count = data.coder_workspace.me.start_count count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/codex/coder" source = "registry.coder.com/coder-labs/codex/coder"
version = "1.0.1" version = "2.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
openai_api_key = "..." openai_api_key = "..."
codex_model = "o4-mini"
install_codex = true
codex_version = "latest"
folder = "/home/coder/project" folder = "/home/coder/project"
codex_system_prompt = "You are a helpful coding assistant. Start every response with `Codex says:`"
} }
``` ```
- Example usage with Tasks: ### Tasks integration
```tf ```tf
# This
data "coder_parameter" "ai_prompt" { data "coder_parameter" "ai_prompt" {
type = "string" type = "string"
name = "AI Prompt" name = "AI Prompt"
@ -65,78 +60,74 @@ module "coder-login" {
module "codex" { module "codex" {
source = "registry.coder.com/coder-labs/codex/coder" source = "registry.coder.com/coder-labs/codex/coder"
version = "2.0.0"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
openai_api_key = "..." openai_api_key = "..."
ai_prompt = data.coder_parameter.ai_prompt.value ai_prompt = data.coder_parameter.ai_prompt.value
folder = "/home/coder/project" folder = "/home/coder/project"
approval_policy = "never" # Full auto mode
# Custom configuration for full auto mode
base_config_toml = <<-EOT
approval_policy = "never"
preferred_auth_method = "apikey"
EOT
} }
``` ```
> [!WARNING] > [!WARNING]
> **Security Notice**: This module configures Codex with a `workspace-write` sandbox that allows AI tasks to read/write files in the specified folder. While the sandbox provides security boundaries, Codex can still modify files within the workspace. Use this module in trusted environments and be aware of the security implications. > This module configures Codex with a `workspace-write` sandbox that allows AI tasks to read/write files in the specified folder. While the sandbox provides security boundaries, Codex can still modify files within the workspace. Use this module _only_ in trusted environments and be aware of the security implications.
## How it Works ## How it Works
- **Install**: The module installs Codex CLI and sets up the environment - **Install**: The module installs Codex CLI and sets up the environment
- **System Prompt**: If `codex_system_prompt` and `folder` are set, creates the directory (if needed) and writes the prompt to `AGENTS.md` - **System Prompt**: If `codex_system_prompt` is set, writes the prompt to `AGENTS.md` in the `~/.codex/` directory
- **Start**: Launches Codex CLI in the specified directory, wrapped by AgentAPI - **Start**: Launches Codex CLI in the specified directory, wrapped by AgentAPI
- **Configuration**: Sets `OPENAI_API_KEY` environment variable and passes `--model` flag to Codex CLI (if variables provided) - **Configuration**: Sets `OPENAI_API_KEY` environment variable and passes `--model` flag to Codex CLI (if variables provided)
## Sandbox Configuration ## Configuration
The module automatically configures Codex with a secure sandbox that allows AI tasks to work effectively: ### Default Configuration
- **Sandbox Mode**: `workspace-write` - Allows Codex to read/write files in the specified `folder` When no custom `base_config_toml` is provided, the module uses these secure defaults:
- **Approval Policy**: `on-request` - Codex asks for permission before performing potentially risky operations
- **Network Access**: Enabled within the workspace for package installation and API calls
### Customizing Sandbox Behavior ```toml
sandbox_mode = "workspace-write"
approval_policy = "never"
preferred_auth_method = "apikey"
You can customize the sandbox behavior using dedicated variables: [sandbox_workspace_write]
network_access = true
#### **Using Dedicated Variables (Recommended)**
For most use cases, use the dedicated sandbox variables:
```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
# ... other variables ...
# Containerized environments (fixes Landlock errors)
sandbox_mode = "danger-full-access"
# Or for read-only mode
# sandbox_mode = "read-only"
# Or for full auto mode
# approval_policy = "never"
# Or disable network access
# network_access = false
}
``` ```
#### **Using extra_codex_settings_toml (Advanced)** ### Custom Configuration
For advanced configuration or when you need to override multiple settings: For custom Codex configuration, use `base_config_toml` and/or `additional_mcp_servers`:
```tf ```tf
module "codex" { module "codex" {
source = "registry.coder.com/coder-labs/codex/coder" source = "registry.coder.com/coder-labs/codex/coder"
version = "2.0.0"
# ... other variables ... # ... other variables ...
extra_codex_settings_toml = <<-EOT # Override default configuration
# Any custom Codex configuration base_config_toml = <<-EOT
model = "gpt-4" sandbox_mode = "danger-full-access"
disable_response_storage = true approval_policy = "never"
preferred_auth_method = "apikey"
EOT
# Add extra MCP servers
additional_mcp_servers = <<-EOT
[mcp_servers.GitHub]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-github"]
type = "stdio"
EOT EOT
} }
``` ```
> [!NOTE] > [!NOTE]
> The dedicated variables (`sandbox_mode`, `approval_policy`, `network_access`) are the recommended way to configure sandbox behavior. Use `extra_codex_settings_toml` only for advanced configuration that isn't covered by the dedicated variables. > If no custom configuration is provided, the module uses secure defaults. The Coder MCP server is always included automatically. For containerized workspaces (Docker/Kubernetes), you may need `sandbox_mode = "danger-full-access"` to avoid permission issues. For advanced options, see [Codex config docs](https://github.com/openai/codex/blob/main/codex-rs/config.md).
## Troubleshooting ## Troubleshooting
@ -150,6 +141,6 @@ module "codex" {
## References ## References
- [OpenAI API Documentation](https://platform.openai.com/docs) - [Codex CLI Documentation](https://github.com/openai/codex)
- [AgentAPI Documentation](https://github.com/coder/agentapi) - [AgentAPI Documentation](https://github.com/coder/agentapi)
- [Coder AI Agents Guide](https://coder.com/docs/tutorials/ai-agents) - [Coder AI Agents Guide](https://coder.com/docs/tutorials/ai-agents)

View File

@ -108,24 +108,25 @@ describe("codex", async () => {
await expectAgentAPIStarted(id); await expectAgentAPIStarted(id);
}); });
test("codex-config-toml", async () => { test("base-config-toml", async () => {
const settings = dedent` const baseConfig = dedent`
[mcp_servers.CustomMCP] sandbox_mode = "danger-full-access"
command = "/Users/jkmr/Documents/work/coder/coder_darwin_arm64" approval_policy = "never"
args = ["exp", "mcp", "server", "app-status-slug=codex"] preferred_auth_method = "apikey"
env = { "CODER_MCP_APP_STATUS_SLUG" = "codex", "CODER_MCP_AI_AGENTAPI_URL"= "http://localhost:3284" }
description = "Report ALL tasks and statuses (in progress, done, failed) you are working on." [custom_section]
enabled = true new_feature = true
type = "stdio"
`.trim(); `.trim();
const { id } = await setup({ const { id } = await setup({
moduleVariables: { moduleVariables: {
extra_codex_settings_toml: settings, base_config_toml: baseConfig,
}, },
}); });
await execModuleScript(id); await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/.codex/config.toml"); const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
expect(resp).toContain("[mcp_servers.CustomMCP]"); expect(resp).toContain("sandbox_mode = \"danger-full-access\"");
expect(resp).toContain("preferred_auth_method = \"apikey\"");
expect(resp).toContain("[custom_section]");
expect(resp).toContain("[mcp_servers.Coder]"); expect(resp).toContain("[mcp_servers.Coder]");
}); });
@ -142,7 +143,7 @@ describe("codex", async () => {
id, id,
"/home/coder/.codex-module/agentapi-start.log", "/home/coder/.codex-module/agentapi-start.log",
); );
expect(resp).toContain("openai_api_key provided !"); expect(resp).toContain("OpenAI API Key: Provided");
}); });
test("pre-post-install-scripts", async () => { test("pre-post-install-scripts", async () => {
@ -181,25 +182,106 @@ describe("codex", async () => {
expect(resp).toContain(folder); expect(resp).toContain(folder);
}); });
test("additional-extensions", async () => { test("additional-mcp-servers", async () => {
const additional = dedent` const additional = dedent`
[mcp_servers.CustomMCP] [mcp_servers.GitHub]
command = "/Users/jkmr/Documents/work/coder/coder_darwin_arm64" command = "npx"
args = ["exp", "mcp", "server", "app-status-slug=codex"] args = ["-y", "@modelcontextprotocol/server-github"]
env = { "CODER_MCP_APP_STATUS_SLUG" = "codex", "CODER_MCP_AI_AGENTAPI_URL"= "http://localhost:3284" }
description = "Report ALL tasks and statuses (in progress, done, failed) you are working on."
enabled = true
type = "stdio" type = "stdio"
description = "GitHub integration"
[mcp_servers.FileSystem]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
type = "stdio"
description = "File system access"
`.trim(); `.trim();
const { id } = await setup({ const { id } = await setup({
moduleVariables: { moduleVariables: {
additional_extensions: additional, additional_mcp_servers: additional,
}, },
}); });
await execModuleScript(id); await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/.codex/config.toml"); const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
expect(resp).toContain("[mcp_servers.CustomMCP]"); expect(resp).toContain("[mcp_servers.GitHub]");
expect(resp).toContain("[mcp_servers.FileSystem]");
expect(resp).toContain("[mcp_servers.Coder]"); expect(resp).toContain("[mcp_servers.Coder]");
expect(resp).toContain("GitHub integration");
});
test("full-custom-config", async () => {
const baseConfig = dedent`
sandbox_mode = "read-only"
approval_policy = "untrusted"
preferred_auth_method = "chatgpt"
custom_setting = "test-value"
[advanced_settings]
timeout = 30000
debug = true
logging_level = "verbose"
`.trim();
const additionalMCP = dedent`
[mcp_servers.CustomTool]
command = "/usr/local/bin/custom-tool"
args = ["--serve", "--port", "8080"]
type = "stdio"
description = "Custom development tool"
[mcp_servers.DatabaseMCP]
command = "python"
args = ["-m", "database_mcp_server"]
type = "stdio"
description = "Database query interface"
`.trim();
const { id } = await setup({
moduleVariables: {
base_config_toml: baseConfig,
additional_mcp_servers: additionalMCP,
},
});
await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
// Check base config
expect(resp).toContain("sandbox_mode = \"read-only\"");
expect(resp).toContain("preferred_auth_method = \"chatgpt\"");
expect(resp).toContain("custom_setting = \"test-value\"");
expect(resp).toContain("[advanced_settings]");
expect(resp).toContain("logging_level = \"verbose\"");
// Check MCP servers
expect(resp).toContain("[mcp_servers.Coder]");
expect(resp).toContain("[mcp_servers.CustomTool]");
expect(resp).toContain("[mcp_servers.DatabaseMCP]");
expect(resp).toContain("Custom development tool");
expect(resp).toContain("Database query interface");
});
test("minimal-default-config", async () => {
const { id } = await setup({
moduleVariables: {
// No base_config_toml or additional_mcp_servers - should use defaults
},
});
await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/.codex/config.toml");
// Check default base config
expect(resp).toContain("sandbox_mode = \"workspace-write\"");
expect(resp).toContain("approval_policy = \"never\"");
expect(resp).toContain("[sandbox_workspace_write]");
expect(resp).toContain("network_access = true");
// Check only Coder MCP server is present
expect(resp).toContain("[mcp_servers.Coder]");
expect(resp).toContain("Report ALL tasks and statuses");
// Ensure no additional MCP servers
const mcpServerCount = (resp.match(/\[mcp_servers\./g) || []).length;
expect(mcpServerCount).toBe(1);
}); });
test("codex-system-prompt", async () => { test("codex-system-prompt", async () => {
@ -210,7 +292,7 @@ describe("codex", async () => {
}, },
}); });
await execModuleScript(id); await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/AGENTS.md"); const resp = await readFileContainer(id, "/home/coder/.codex/AGENTS.md");
expect(resp).toContain(prompt); expect(resp).toContain(prompt);
}); });
@ -223,7 +305,8 @@ describe("codex", async () => {
`.trim(); `.trim();
const pre_install_script = dedent` const pre_install_script = dedent`
#!/bin/bash #!/bin/bash
echo -e "${prompt_3}" >> /home/coder/AGENTS.md mkdir -p /home/coder/.codex
echo -e "${prompt_3}" >> /home/coder/.codex/AGENTS.md
`.trim(); `.trim();
const { id } = await setup({ const { id } = await setup({
@ -233,7 +316,7 @@ describe("codex", async () => {
}, },
}); });
await execModuleScript(id); await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/AGENTS.md"); const resp = await readFileContainer(id, "/home/coder/.codex/AGENTS.md");
expect(resp).toContain(prompt_1); expect(resp).toContain(prompt_1);
expect(resp).toContain(prompt_2); expect(resp).toContain(prompt_2);
@ -245,7 +328,7 @@ describe("codex", async () => {
}, },
}); });
await execModuleScript(id_2); await execModuleScript(id_2);
const resp_2 = await readFileContainer(id_2, "/home/coder/AGENTS.md"); const resp_2 = await readFileContainer(id_2, "/home/coder/.codex/AGENTS.md");
expect(resp_2).toContain(prompt_1); expect(resp_2).toContain(prompt_1);
const count = (resp_2.match(new RegExp(prompt_1, "g")) || []).length; const count = (resp_2.match(new RegExp(prompt_1, "g")) || []).length;
expect(count).toBe(1); expect(count).toBe(1);
@ -268,12 +351,16 @@ describe("codex", async () => {
}); });
test("start-without-prompt", async () => { test("start-without-prompt", async () => {
const { id } = await setup(); const { id } = await setup({
moduleVariables: {
codex_system_prompt: "", // Explicitly disable system prompt
},
});
await execModuleScript(id); await execModuleScript(id);
const prompt = await execContainer(id, [ const prompt = await execContainer(id, [
"ls", "ls",
"-l", "-l",
"/home/coder/AGENTS.md", "/home/coder/.codex/AGENTS.md",
]); ]);
expect(prompt.exitCode).not.toBe(0); expect(prompt.exitCode).not.toBe(0);
expect(prompt.stderr).toContain("No such file or directory"); expect(prompt.stderr).toContain("No such file or directory");

View File

@ -53,41 +53,21 @@ variable "codex_version" {
default = "" # empty string means the latest available version default = "" # empty string means the latest available version
} }
variable "extra_codex_settings_toml" { variable "base_config_toml" {
type = string type = string
description = "Settings to append to ~/.codex/config.toml." description = "Complete base TOML configuration for Codex (without mcp_servers section). If empty, uses minimal default configuration with workspace-write sandbox mode and never approval policy. For advanced options, see https://github.com/openai/codex/blob/main/codex-rs/config.md"
default = "" default = ""
} }
variable "sandbox_mode" { variable "additional_mcp_servers" {
type = string type = string
description = "The sandbox mode for Codex. Options: workspace-write, read-only, danger-full-access." description = "Additional MCP servers configuration in TOML format. These will be merged with the required Coder MCP server in the [mcp_servers] section."
default = "workspace-write" default = ""
validation {
condition = contains(["workspace-write", "read-only", "danger-full-access"], var.sandbox_mode)
error_message = "sandbox_mode must be one of: workspace-write, read-only, danger-full-access."
}
}
variable "approval_policy" {
type = string
description = "The approval policy for Codex. Options: on-request, never, untrusted."
default = "on-request"
validation {
condition = contains(["on-request", "never", "untrusted"], var.approval_policy)
error_message = "approval_policy must be one of: on-request, never, untrusted."
}
}
variable "network_access" {
type = bool
description = "Whether to allow network access in workspace-write mode."
default = true
} }
variable "openai_api_key" { variable "openai_api_key" {
type = string type = string
description = "Codex API Key" description = "OpenAI API key for Codex CLI"
default = "" default = ""
} }
@ -105,7 +85,7 @@ variable "agentapi_version" {
variable "codex_model" { variable "codex_model" {
type = string type = string
description = "The model for Codex to use (e.g., o4-mini)." description = "The model for Codex to use. Defaults to gpt-5."
default = "" default = ""
} }
@ -123,24 +103,16 @@ variable "post_install_script" {
variable "ai_prompt" { variable "ai_prompt" {
type = string type = string
description = "Task prompt for the Codex CLI" description = "Initial task prompt for Codex CLI when launched via Tasks"
default = ""
}
variable "additional_extensions" {
type = string
description = "Additional extensions configuration in json format to append to the config."
default = "" default = ""
} }
variable "codex_system_prompt" { variable "codex_system_prompt" {
type = string type = string
description = "System prompt for Codex. It will be added to AGENTS.md in the specified folder." description = "System instructions written to AGENTS.md in the ~/.codex directory"
default = "" default = "You are a helpful coding assistant. Start every response with `Codex says:`"
} }
resource "coder_env" "openai_api_key" { resource "coder_env" "openai_api_key" {
agent_id = var.agent_id agent_id = var.agent_id
name = "OPENAI_API_KEY" name = "OPENAI_API_KEY"
@ -194,14 +166,11 @@ module "agentapi" {
chmod +x /tmp/install.sh chmod +x /tmp/install.sh
ARG_INSTALL='${var.install_codex}' \ ARG_INSTALL='${var.install_codex}' \
ARG_CODEX_VERSION='${var.codex_version}' \ ARG_CODEX_VERSION='${var.codex_version}' \
ARG_EXTRA_CODEX_CONFIG='${base64encode(var.extra_codex_settings_toml)}' \ ARG_BASE_CONFIG_TOML='${base64encode(var.base_config_toml)}' \
ARG_ADDITIONAL_MCP_SERVERS='${base64encode(var.additional_mcp_servers)}' \
ARG_CODER_MCP_APP_STATUS_SLUG='${local.app_slug}' \ ARG_CODER_MCP_APP_STATUS_SLUG='${local.app_slug}' \
ARG_ADDITIONAL_EXTENSIONS='${base64encode(var.additional_extensions)}' \
ARG_CODEX_START_DIRECTORY='${var.folder}' \ ARG_CODEX_START_DIRECTORY='${var.folder}' \
ARG_CODEX_INSTRUCTION_PROMPT='${base64encode(var.codex_system_prompt)}' \ ARG_CODEX_INSTRUCTION_PROMPT='${base64encode(var.codex_system_prompt)}' \
ARG_SANDBOX_MODE='${var.sandbox_mode}' \
ARG_APPROVAL_POLICY='${var.approval_policy}' \
ARG_NETWORK_ACCESS='${var.network_access}' \
/tmp/install.sh /tmp/install.sh
EOT EOT
} }

View File

@ -3,7 +3,6 @@ source "$HOME"/.bashrc
BOLD='\033[0;1m' BOLD='\033[0;1m'
# Function to check if a command exists
command_exists() { command_exists() {
command -v "$1" > /dev/null 2>&1 command -v "$1" > /dev/null 2>&1
} }
@ -11,25 +10,23 @@ set -o errexit
set -o pipefail set -o pipefail
set -o nounset set -o nounset
ARG_EXTRA_CODEX_CONFIG=$(echo -n "$ARG_EXTRA_CODEX_CONFIG" | base64 -d) ARG_BASE_CONFIG_TOML=$(echo -n "$ARG_BASE_CONFIG_TOML" | base64 -d)
ARG_ADDITIONAL_EXTENSIONS=$(echo -n "$ARG_ADDITIONAL_EXTENSIONS" | base64 -d) ARG_ADDITIONAL_MCP_SERVERS=$(echo -n "$ARG_ADDITIONAL_MCP_SERVERS" | base64 -d)
ARG_CODEX_INSTRUCTION_PROMPT=$(echo -n "$ARG_CODEX_INSTRUCTION_PROMPT" | base64 -d) ARG_CODEX_INSTRUCTION_PROMPT=$(echo -n "$ARG_CODEX_INSTRUCTION_PROMPT" | base64 -d)
echo "--------------------------------" echo "=== Codex Module Configuration ==="
printf "install: %s\n" "$ARG_INSTALL" printf "Install Codex: %s\n" "$ARG_INSTALL"
printf "codex_version: %s\n" "$ARG_CODEX_VERSION" printf "Codex Version: %s\n" "$ARG_CODEX_VERSION"
printf "codex_config: %s\n" "$ARG_EXTRA_CODEX_CONFIG" printf "App Slug: %s\n" "$ARG_CODER_MCP_APP_STATUS_SLUG"
printf "app_slug: %s\n" "$ARG_CODER_MCP_APP_STATUS_SLUG" printf "Start Directory: %s\n" "$ARG_CODEX_START_DIRECTORY"
printf "additional_extensions: %s\n" "$ARG_ADDITIONAL_EXTENSIONS" printf "Has Base Config: %s\n" "$([ -n "$ARG_BASE_CONFIG_TOML" ] && echo "Yes" || echo "No")"
printf "start_directory: %s\n" "$ARG_CODEX_START_DIRECTORY" printf "Has Additional MCP: %s\n" "$([ -n "$ARG_ADDITIONAL_MCP_SERVERS" ] && echo "Yes" || echo "No")"
printf "instruction_prompt: %s\n" "$ARG_CODEX_INSTRUCTION_PROMPT" printf "Has System Prompt: %s\n" "$([ -n "$ARG_CODEX_INSTRUCTION_PROMPT" ] && echo "Yes" || echo "No")"
echo "======================================"
echo "--------------------------------"
set +o nounset set +o nounset
function install_node() { function install_node() {
# borrowed from claude-code module
if ! command_exists npm; then if ! command_exists npm; then
printf "npm not found, checking for Node.js installation...\n" printf "npm not found, checking for Node.js installation...\n"
if ! command_exists node; then if ! command_exists node; then
@ -58,24 +55,18 @@ function install_node() {
function install_codex() { function install_codex() {
if [ "${ARG_INSTALL}" = "true" ]; then if [ "${ARG_INSTALL}" = "true" ]; then
# we need node to install and run codex-cli
install_node 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 if ! command_exists nvm; then
printf "which node: %s\n" "$(which node)" printf "which node: %s\n" "$(which node)"
printf "which npm: %s\n" "$(which npm)" printf "which npm: %s\n" "$(which npm)"
# Create a directory for global packages
mkdir -p "$HOME"/.npm-global mkdir -p "$HOME"/.npm-global
# Configure npm to use it
npm config set prefix "$HOME/.npm-global" npm config set prefix "$HOME/.npm-global"
# Add to PATH for current session
export PATH="$HOME/.npm-global/bin:$PATH" 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 if ! grep -q "export PATH=$HOME/.npm-global/bin:\$PATH" ~/.bashrc; then
echo "export PATH=$HOME/.npm-global/bin:\$PATH" >> ~/.bashrc echo "export PATH=$HOME/.npm-global/bin:\$PATH" >> ~/.bashrc
fi fi
@ -92,79 +83,82 @@ function install_codex() {
fi fi
} }
function populate_config_toml() { write_minimal_default_config() {
CONFIG_PATH="$HOME/.codex/config.toml" local config_path="$1"
mkdir -p "$(dirname "$CONFIG_PATH")" cat << EOF > "$config_path"
printf "Custom codex_config is provided !\n" # Minimal Default Codex Configuration
BASE_SANDBOX_CONFIG=$( sandbox_mode = "workspace-write"
cat << EOF approval_policy = "never"
# Base sandbox configuration for Codex workspace access preferred_auth_method = "apikey"
# This ensures Codex can read/write files in the specified folder for AI tasks
sandbox_mode = "${ARG_SANDBOX_MODE}"
approval_policy = "${ARG_APPROVAL_POLICY}"
# Allow network access in workspace-write mode for package installation, API calls, etc.
[sandbox_workspace_write] [sandbox_workspace_write]
network_access = ${ARG_NETWORK_ACCESS} network_access = true
EOF
)
BASE_EXTENSIONS=$( EOF
cat << EOF }
append_mcp_servers_section() {
local config_path="$1"
cat << EOF >> "$config_path"
# MCP Servers Configuration
[mcp_servers.Coder] [mcp_servers.Coder]
command = "coder" command = "coder"
args = ["exp", "mcp", "server"] args = ["exp", "mcp", "server"]
env = { "CODER_MCP_APP_STATUS_SLUG" = "${ARG_CODER_MCP_APP_STATUS_SLUG}", "CODER_MCP_AI_AGENTAPI_URL"= "http://localhost:3284", "CODER_AGENT_URL" = "${CODER_AGENT_URL}", "CODER_AGENT_TOKEN" = "${CODER_AGENT_TOKEN}" } env = { "CODER_MCP_APP_STATUS_SLUG" = "${ARG_CODER_MCP_APP_STATUS_SLUG}", "CODER_MCP_AI_AGENTAPI_URL" = "http://localhost:3284", "CODER_AGENT_URL" = "${CODER_AGENT_URL}", "CODER_AGENT_TOKEN" = "${CODER_AGENT_TOKEN}" }
description = "Report ALL tasks and statuses (in progress, done, failed) you are working on." description = "Report ALL tasks and statuses (in progress, done, failed) you are working on."
type = "stdio" type = "stdio"
EOF EOF
)
echo " if [ -n "$ARG_ADDITIONAL_MCP_SERVERS" ]; then
${BASE_SANDBOX_CONFIG} printf "Adding additional MCP servers\n"
echo "$ARG_ADDITIONAL_MCP_SERVERS" >> "$config_path"
fi
}
${ARG_EXTRA_CODEX_CONFIG} function populate_config_toml() {
CONFIG_PATH="$HOME/.codex/config.toml"
mkdir -p "$(dirname "$CONFIG_PATH")"
${BASE_EXTENSIONS} if [ -n "$ARG_BASE_CONFIG_TOML" ]; then
printf "Using provided base configuration\n"
${ARG_ADDITIONAL_EXTENSIONS} echo "$ARG_BASE_CONFIG_TOML" > "$CONFIG_PATH"
" > "$HOME/.codex/config.toml" else
printf "Using minimal default configuration\n"
write_minimal_default_config "$CONFIG_PATH"
fi
append_mcp_servers_section "$CONFIG_PATH"
} }
function add_instruction_prompt_if_exists() { function add_instruction_prompt_if_exists() {
if [ -n "${ARG_CODEX_INSTRUCTION_PROMPT:-}" ]; then if [ -n "${ARG_CODEX_INSTRUCTION_PROMPT:-}" ]; then
if [ -d "${ARG_CODEX_START_DIRECTORY}" ]; then AGENTS_PATH="$HOME/.codex/AGENTS.md"
printf "Directory '%s' exists. Changing to it.\\n" "${ARG_CODEX_START_DIRECTORY}" printf "Creating AGENTS.md in .codex directory: %s\\n" "${AGENTS_PATH}"
cd "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not change to directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}" mkdir -p "$HOME/.codex"
exit 1
} if [ -f "${AGENTS_PATH}" ] && grep -Fq "${ARG_CODEX_INSTRUCTION_PROMPT}" "${AGENTS_PATH}"; then
printf "AGENTS.md already contains the instruction prompt. Skipping append.\n"
else else
printf "Directory '%s' does not exist. Creating and changing to it.\\n" "${ARG_CODEX_START_DIRECTORY}" printf "Appending instruction prompt to AGENTS.md in .codex directory\n"
echo -e "\n${ARG_CODEX_INSTRUCTION_PROMPT}" >> "${AGENTS_PATH}"
fi
if [ ! -d "${ARG_CODEX_START_DIRECTORY}" ]; then
printf "Creating start directory '%s'\\n" "${ARG_CODEX_START_DIRECTORY}"
mkdir -p "${ARG_CODEX_START_DIRECTORY}" || { mkdir -p "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not create directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}" printf "Error: Could not create directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
exit 1 exit 1
} }
cd "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not change to directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
exit 1
}
fi
# Check if AGENTS.md contains the instruction prompt already
if [ -f AGENTS.md ] && grep -Fxq "${ARG_CODEX_INSTRUCTION_PROMPT}" AGENTS.md; then
printf "AGENTS.md already contains the instruction prompt. Skipping append.\n"
else
printf "Appending instruction prompt to AGENTS.md\n"
echo -e "\n${ARG_CODEX_INSTRUCTION_PROMPT}" >> AGENTS.md
fi fi
else else
printf "AGENTS.md is not set.\n" printf "AGENTS.md instruction prompt is not set.\n"
fi fi
} }
# Install Codex
install_codex install_codex
codex --version codex --version
populate_config_toml populate_config_toml

View File

@ -1,6 +1,5 @@
#!/bin/bash #!/bin/bash
# Load shell environment
source "$HOME"/.bashrc source "$HOME"/.bashrc
set -o errexit set -o errexit
set -o pipefail set -o pipefail
@ -18,12 +17,12 @@ printf "Version: %s\n" "$(codex --version)"
set -o nounset set -o nounset
ARG_CODEX_TASK_PROMPT=$(echo -n "$ARG_CODEX_TASK_PROMPT" | base64 -d) ARG_CODEX_TASK_PROMPT=$(echo -n "$ARG_CODEX_TASK_PROMPT" | base64 -d)
echo "--------------------------------" echo "=== Codex Launch Configuration ==="
printf "openai_api_key: %s\n" "$ARG_OPENAI_API_KEY" printf "OpenAI API Key: %s\n" "$([ -n "$ARG_OPENAI_API_KEY" ] && echo "Provided" || echo "Not provided")"
printf "codex_model: %s\n" "$ARG_CODEX_MODEL" printf "Codex Model: %s\n" "${ARG_CODEX_MODEL:-"Default"}"
printf "start_directory: %s\n" "$ARG_CODEX_START_DIRECTORY" printf "Start Directory: %s\n" "$ARG_CODEX_START_DIRECTORY"
printf "task_prompt: %s\n" "$ARG_CODEX_TASK_PROMPT" printf "Has Task Prompt: %s\n" "$([ -n "$ARG_CODEX_TASK_PROMPT" ] && echo "Yes" || echo "No")"
echo "--------------------------------" echo "======================================"
set +o nounset set +o nounset
CODEX_ARGS=() CODEX_ARGS=()
@ -66,13 +65,9 @@ else
printf "No task prompt given.\n" printf "No task prompt given.\n"
fi fi
if [ -n "$ARG_OPENAI_API_KEY" ]; then
printf "openai_api_key provided !\n"
else
printf "openai_api_key not provided\n"
fi
# use low width to fit in the tasks UI sidebar # Terminal dimensions optimized for Coder Tasks UI sidebar:
# we adjust the height to 930 due to a bug in codex, see: https://github.com/openai/codex/issues/1608 # - Width 67: fits comfortably in sidebar
printf "Starting codex with %s\n" "${CODEX_ARGS[@]}" # - Height 1190: adjusted due to Codex terminal height bug
printf "Starting Codex with arguments: %s\n" "${CODEX_ARGS[*]}"
agentapi server --term-width 67 --term-height 1190 -- codex "${CODEX_ARGS[@]}" agentapi server --term-width 67 --term-height 1190 -- codex "${CODEX_ARGS[@]}"