Compare commits

..

7 Commits

Author SHA1 Message Date
DevCats eddcfb6627 Update MODULE.md
Co-authored-by: Atif Ali <atif@coder.com>
2025-06-25 19:44:19 -05:00
DevCats 6bcb1ac50b Update MODULE.md
Co-authored-by: Atif Ali <atif@coder.com>
2025-06-25 19:44:07 -05:00
DevCats 4bef92b1f6 Update MODULE.md
Co-authored-by: Atif Ali <atif@coder.com>
2025-06-25 19:43:57 -05:00
DevelopmentCats 6763fdbbbb feat: Add Documentation for Coder Modules 2025-06-24 20:12:39 +00:00
DevCats e5ccf74ccc feat: claude-code workspace persistence (#154)
## Description

Add Tmux Plugin Manager with resurrect and continuum plugins. Add
functionality to be able to enable workspace persistence to save the
tmux session automatically so that it can persist through workspace
restarts.

---

## Type of Change

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

---

## Module Information

**Path:** `registry/coder/modules/claude-code`  
**New version:** `v1.4.0`  
**Breaking change:** [ ] Yes [X] No

---

## Testing & Validation

- [X] Tests pass (`bun test`)
- [X] Code formatted (`bun run fmt`)
- [X] Changes tested locally

---

## Related Issues

Closes [#29](https://github.com/coder/registry/issues/29)
2025-06-19 16:46:18 -05:00
DevCats a47ff911e1 fix: Version-Bump Workflow/Script - Formatting before diff (#155)
## Description

Set up Pre-Req's, and ensure that formatting is done before checking
diff since it likes to not respect prettier formatting.

---

## Type of Change

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

---

## Testing & Validation

- [X] Tests pass (`bun test`)
- [X] Code formatted (`bun run fmt`)
- [ ] Changes tested locally

---------

Co-authored-by: Atif Ali <atif@coder.com>
2025-06-19 16:35:10 -05:00
Ben Potter a8e23647c5 feat: add option to disable VS Code Web workspace trust protection (#131)
for admins with certainty about what is installed in the environment,
this is ideal. otherwise, it's best to get user trust

---------

Co-authored-by: DevelopmentCats <christofer@coder.com>
Co-authored-by: Atif Ali <atif@coder.com>
2025-06-16 21:24:51 -05:00
8 changed files with 1394 additions and 128 deletions
+9
View File
@@ -190,6 +190,15 @@ main() {
done <<< "$modules"
# Always run formatter to ensure consistent formatting
echo "🔧 Running formatter to ensure consistent formatting..."
if command -v bun >/dev/null 2>&1; then
bun fmt >/dev/null 2>&1 || echo "⚠️ Warning: bun fmt failed, but continuing..."
else
echo "⚠️ Warning: bun not found, skipping formatting"
fi
echo ""
echo "📋 Summary:"
echo "Bump Type: $bump_type"
echo ""
+11
View File
@@ -25,6 +25,17 @@ jobs:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Set up Terraform
uses: coder/coder/.github/actions/setup-tf@main
- name: Install dependencies
run: bun install
- name: Extract bump type from label
id: bump-type
run: |
+1200
View File
File diff suppressed because it is too large Load Diff
+26 -3
View File
@@ -14,7 +14,7 @@ Run the [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "1.3.1"
version = "1.4.0"
agent_id = coder_agent.example.id
folder = "/home/coder"
install_claude_code = true
@@ -88,7 +88,7 @@ resource "coder_agent" "main" {
module "claude-code" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/claude-code/coder"
version = "1.3.1"
version = "1.4.0"
agent_id = coder_agent.example.id
folder = "/home/coder"
install_claude_code = true
@@ -100,6 +100,29 @@ module "claude-code" {
}
```
## Session Persistence (Experimental)
Enable automatic session persistence to maintain Claude Code sessions across workspace restarts:
```tf
module "claude-code" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/claude-code/coder"
version = "1.4.0"
agent_id = coder_agent.example.id
folder = "/home/coder"
install_claude_code = true
# Enable tmux with session persistence
experiment_use_tmux = true
experiment_tmux_session_persistence = true
experiment_tmux_session_save_interval = "10" # Save every 10 minutes
experiment_report_tasks = true
}
```
Session persistence automatically saves and restores your Claude Code environment, including working directory and command history.
## Run standalone
Run Claude Code as a standalone app in your workspace. This will install Claude Code and run it directly without using screen or any task reporting to the Coder UI.
@@ -107,7 +130,7 @@ Run Claude Code as a standalone app in your workspace. This will install Claude
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "1.3.1"
version = "1.4.0"
agent_id = coder_agent.example.id
folder = "/home/coder"
install_claude_code = true
+128 -118
View File
@@ -3,8 +3,8 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.7.0"
source = "coder/coder"
version = ">= 2.5"
}
}
}
@@ -54,18 +54,6 @@ variable "claude_code_version" {
default = "latest"
}
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.2"
}
variable "experiment_use_screen" {
type = bool
description = "Whether to use screen for running Claude Code in the background."
@@ -96,18 +84,21 @@ variable "experiment_post_install_script" {
default = null
}
variable "experiment_tmux_session_persistence" {
type = bool
description = "Whether to enable tmux session persistence across workspace restarts."
default = false
}
variable "experiment_tmux_session_save_interval" {
type = string
description = "How often to save tmux sessions in minutes."
default = "15"
}
locals {
encoded_pre_install_script = var.experiment_pre_install_script != null ? base64encode(var.experiment_pre_install_script) : ""
encoded_post_install_script = var.experiment_post_install_script != null ? base64encode(var.experiment_post_install_script) : ""
agentapi_start_command = <<-EOT
#!/bin/bash
set -e
# use low width to fit in the tasks UI sidebar. height is adjusted to ~match the default 80k (80x1000) characters
# visible in the terminal screen.
agentapi server --term-width 67 --term-height 1190 -- bash -c "claude --dangerously-skip-permissions \"$(cat ~/.claude-code-prompt)\""
EOT
agentapi_start_command_base64 = base64encode(local.agentapi_start_command)
}
# Install and Initialize Claude Code
@@ -119,12 +110,28 @@ resource "coder_script" "claude_code" {
#!/bin/bash
set -e
# Function to check if a command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
# Check if the specified folder exists
install_tmux() {
echo "Installing tmux..."
if command_exists apt-get; then
sudo apt-get update && sudo apt-get install -y tmux
elif command_exists yum; then
sudo yum install -y tmux
elif command_exists dnf; then
sudo dnf install -y tmux
elif command_exists pacman; then
sudo pacman -S --noconfirm tmux
elif command_exists apk; then
sudo apk add tmux
else
echo "Error: Unable to install tmux automatically. Package manager not recognized."
exit 1
fi
}
if [ ! -d "${var.folder}" ]; then
echo "Warning: The specified folder '${var.folder}' does not exist."
echo "Creating the folder..."
@@ -133,8 +140,6 @@ resource "coder_script" "claude_code" {
mkdir -p "${var.folder}"
echo "Folder created successfully."
fi
# Run pre-install script if provided
if [ -n "${local.encoded_pre_install_script}" ]; then
echo "Running pre-install script..."
echo "${local.encoded_pre_install_script}" | base64 -d > /tmp/pre_install.sh
@@ -142,48 +147,40 @@ resource "coder_script" "claude_code" {
/tmp/pre_install.sh
fi
# Install Claude Code if enabled
if [ "${var.install_claude_code}" = "true" ]; then
if ! command_exists npm; then
echo "Error: npm is not installed. Please install Node.js and npm first."
exit 1
echo "npm not found, checking for Node.js installation..."
if ! command_exists node; then
echo "Node.js not found, installing Node.js via NVM..."
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
echo "Node.js installed: $(node --version)"
echo "npm installed: $(npm --version)"
else
echo "Node.js is installed but npm is not available. Please install npm manually."
exit 1
fi
fi
echo "Installing Claude Code..."
npm install -g @anthropic-ai/claude-code@${var.claude_code_version}
fi
# Install AgentAPI if enabled
if [ "${var.install_agentapi}" = "true" ]; then
echo "Installing AgentAPI..."
arch=$(uname -m)
if [ "$arch" = "x86_64" ]; then
binary_name="agentapi-linux-amd64"
elif [ "$arch" = "aarch64" ]; then
binary_name="agentapi-linux-arm64"
else
echo "Error: Unsupported architecture: $arch"
exit 1
fi
wget "https://github.com/coder/agentapi/releases/download/${var.agentapi_version}/$binary_name"
chmod +x "$binary_name"
sudo mv "$binary_name" /usr/local/bin/agentapi
fi
if ! command_exists agentapi; then
echo "Error: AgentAPI is not installed. Please enable install_agentapi or install it manually."
exit 1
fi
echo -n "$CODER_MCP_CLAUDE_TASK_PROMPT" > ~/.claude-code-prompt
echo -n "${local.agentapi_start_command_base64}" | base64 -d > ~/.agentapi-start-command
chmod +x ~/.agentapi-start-command
if [ "${var.experiment_report_tasks}" = "true" ]; then
echo "Configuring Claude Code to report tasks via Coder MCP..."
coder exp mcp configure claude-code ${var.folder} --ai-agentapi-url http://localhost:3284
coder exp mcp configure claude-code ${var.folder}
fi
# Run post-install script if provided
if [ -n "${local.encoded_post_install_script}" ]; then
echo "Running post-install script..."
echo "${local.encoded_post_install_script}" | base64 -d > /tmp/post_install.sh
@@ -191,62 +188,97 @@ resource "coder_script" "claude_code" {
/tmp/post_install.sh
fi
# Handle terminal multiplexer selection (tmux or screen)
if [ "${var.experiment_use_tmux}" = "true" ] && [ "${var.experiment_use_screen}" = "true" ]; then
echo "Error: Both experiment_use_tmux and experiment_use_screen cannot be true simultaneously."
echo "Please set only one of them to true."
exit 1
fi
# Run with tmux if enabled
if [ "${var.experiment_use_tmux}" = "true" ]; then
echo "Running Claude Code in the background with tmux..."
if [ "${var.experiment_tmux_session_persistence}" = "true" ] && [ "${var.experiment_use_tmux}" != "true" ]; then
echo "Error: Session persistence requires tmux to be enabled."
echo "Please set experiment_use_tmux = true when using session persistence."
exit 1
fi
# Check if tmux is installed
if [ "${var.experiment_use_tmux}" = "true" ]; then
if ! command_exists tmux; then
echo "Error: tmux is not installed. Please install tmux manually."
exit 1
install_tmux
fi
touch "$HOME/.claude-code.log"
if [ "${var.experiment_tmux_session_persistence}" = "true" ]; then
echo "Setting up tmux session persistence..."
if ! command_exists git; then
echo "Git not found, installing git..."
if command_exists apt-get; then
sudo apt-get update && sudo apt-get install -y git
elif command_exists yum; then
sudo yum install -y git
elif command_exists dnf; then
sudo dnf install -y git
elif command_exists pacman; then
sudo pacman -S --noconfirm git
elif command_exists apk; then
sudo apk add git
else
echo "Error: Unable to install git automatically. Package manager not recognized."
echo "Please install git manually to enable session persistence."
exit 1
fi
fi
mkdir -p ~/.tmux/plugins
if [ ! -d ~/.tmux/plugins/tpm ]; then
git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm
fi
cat > ~/.tmux.conf << EOF
# Claude Code tmux persistence configuration
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-continuum'
# Configure session persistence
set -g @resurrect-processes ':all:'
set -g @resurrect-capture-pane-contents 'on'
set -g @resurrect-save-bash-history 'on'
set -g @continuum-restore 'on'
set -g @continuum-save-interval '${var.experiment_tmux_session_save_interval}'
set -g @continuum-boot 'on'
set -g @continuum-save-on 'on'
# Initialize plugin manager
run '~/.tmux/plugins/tpm/tpm'
EOF
~/.tmux/plugins/tpm/scripts/install_plugins.sh
fi
echo "Running Claude Code in the background with tmux..."
touch "$HOME/.claude-code.log"
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
# use low width to fit in the tasks UI sidebar. height is adjusted to ~match the default 80k (80x1000) characters
# visible in the terminal screen.
tmux new-session -d -s claude-code-agentapi -c ${var.folder} '~/.agentapi-start-command; exec bash'
echo "Waiting for agentapi server to start on port 3284..."
for i in $(seq 1 15); do
if lsof -i :3284 | grep -q 'LISTEN'; then
echo "agentapi server started on port 3284."
break
if [ "${var.experiment_tmux_session_persistence}" = "true" ]; then
sleep 3
if ! tmux has-session -t claude-code 2>/dev/null; then
# Only create a new session if one doesn't exist
tmux new-session -d -s claude-code -c ${var.folder} "claude --dangerously-skip-permissions \"$CODER_MCP_CLAUDE_TASK_PROMPT\""
fi
else
if ! tmux has-session -t claude-code 2>/dev/null; then
tmux new-session -d -s claude-code -c ${var.folder} "claude --dangerously-skip-permissions \"$CODER_MCP_CLAUDE_TASK_PROMPT\""
fi
echo "Waiting... ($i/15)"
sleep 1
done
if ! lsof -i :3284 | grep -q 'LISTEN'; then
echo "Error: agentapi server did not start on port 3284 after 15 seconds."
exit 1
fi
tmux new-session -d -s claude-code -c ${var.folder} "agentapi attach"
fi
# Run with screen if enabled
if [ "${var.experiment_use_screen}" = "true" ]; then
echo "Running Claude Code in the background..."
# Check if screen is installed
if ! command_exists screen; then
echo "Error: screen is not installed. Please install screen manually."
exit 1
fi
touch "$HOME/.claude-code.log"
# Ensure the screenrc exists
if [ ! -f "$HOME/.screenrc" ]; then
echo "Creating ~/.screenrc and adding multiuser settings..." | tee -a "$HOME/.claude-code.log"
echo -e "multiuser on\nacladd $(whoami)" > "$HOME/.screenrc"
@@ -261,16 +293,16 @@ resource "coder_script" "claude_code" {
echo "Adding 'acladd $(whoami)' to ~/.screenrc..." | tee -a "$HOME/.claude-code.log"
echo "acladd $(whoami)" >> "$HOME/.screenrc"
fi
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
screen -U -dmS claude-code bash -c '
cd ${var.folder}
~/.agentapi-start-command
claude --dangerously-skip-permissions "$CODER_MCP_CLAUDE_TASK_PROMPT" | tee -a "$HOME/.claude-code.log"
exec bash
'
else
# Check if claude is installed before running
if ! command_exists claude; then
echo "Error: Claude Code is not installed. Please enable install_claude_code or install it manually."
exit 1
@@ -280,20 +312,6 @@ resource "coder_script" "claude_code" {
run_on_start = true
}
resource "coder_app" "claude_code_web" {
slug = "claude-code-web"
display_name = "Claude Code Web"
agent_id = var.agent_id
url = "http://localhost:3284/"
icon = var.icon
subdomain = true
healthcheck {
url = "http://localhost:3284/status"
interval = 5
threshold = 3
}
}
resource "coder_app" "claude_code" {
slug = "claude-code"
display_name = "Claude Code"
@@ -306,18 +324,16 @@ resource "coder_app" "claude_code" {
export LC_ALL=en_US.UTF-8
if [ "${var.experiment_use_tmux}" = "true" ]; then
if ! tmux has-session -t claude-code-agentapi 2>/dev/null; then
echo "Starting a new Claude Code agentapi tmux session." | tee -a "$HOME/.claude-code.log"
tmux new-session -d -s claude-code-agentapi -c ${var.folder} '~/.agentapi-start-command; exec bash'
fi
if tmux has-session -t claude-code 2>/dev/null; then
echo "Attaching to existing Claude Code tmux session." | tee -a "$HOME/.claude-code.log"
# If Claude isn't running in the session, start it without the prompt
if ! tmux list-panes -t claude-code -F '#{pane_current_command}' | grep -q "claude"; then
tmux send-keys -t claude-code "cd ${var.folder} && claude -c --dangerously-skip-permissions" C-m
fi
tmux attach-session -t claude-code
else
echo "Starting a new Claude Code tmux session." | tee -a "$HOME/.claude-code.log"
tmux new-session -s claude-code -c ${var.folder} "agentapi attach; exec bash"
tmux new-session -s claude-code -c ${var.folder} "claude --dangerously-skip-permissions | tee -a \"$HOME/.claude-code.log\"; exec bash"
fi
elif [ "${var.experiment_use_screen}" = "true" ]; then
if screen -list | grep -q "claude-code"; then
@@ -325,7 +341,7 @@ resource "coder_app" "claude_code" {
screen -xRR claude-code
else
echo "Starting a new Claude Code screen session." | tee -a "$HOME/.claude-code.log"
screen -S claude-code bash -c 'agentapi attach; exec bash'
screen -S claude-code bash -c 'claude --dangerously-skip-permissions | tee -a "$HOME/.claude-code.log"; exec bash'
fi
else
cd ${var.folder}
@@ -336,9 +352,3 @@ resource "coder_app" "claude_code" {
order = var.order
group = var.group
}
resource "coder_ai_task" "claude_code" {
sidebar_app {
id = coder_app.claude_code.id
}
}
+5 -5
View File
@@ -15,7 +15,7 @@ Automatically install [Visual Studio Code Server](https://code.visualstudio.com/
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.2.0"
version = "1.3.0"
agent_id = coder_agent.example.id
accept_license = true
}
@@ -31,7 +31,7 @@ module "vscode-web" {
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.2.0"
version = "1.3.0"
agent_id = coder_agent.example.id
install_prefix = "/home/coder/.vscode-web"
folder = "/home/coder"
@@ -45,7 +45,7 @@ module "vscode-web" {
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.2.0"
version = "1.3.0"
agent_id = coder_agent.example.id
extensions = ["github.copilot", "ms-python.python", "ms-toolsai.jupyter"]
accept_license = true
@@ -60,7 +60,7 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.2.0"
version = "1.3.0"
agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula"]
settings = {
@@ -78,7 +78,7 @@ By default, this module installs the latest. To pin a specific version, retrieve
module "vscode-web" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/vscode-web/coder"
version = "1.2.0"
version = "1.3.0"
agent_id = coder_agent.example.id
commit_id = "e54c774e0add60467559eb0d1e229c6452cf8447"
accept_license = true
@@ -121,6 +121,12 @@ variable "use_cached" {
default = false
}
variable "disable_trust" {
type = bool
description = "Disables workspace trust protection for VS Code Web."
default = false
}
variable "extensions_dir" {
type = string
description = "Override the directory to store extensions in."
@@ -169,6 +175,7 @@ resource "coder_script" "vscode-web" {
SETTINGS : replace(jsonencode(var.settings), "\"", "\\\""),
OFFLINE : var.offline,
USE_CACHED : var.use_cached,
DISABLE_TRUST : var.disable_trust,
EXTENSIONS_DIR : var.extensions_dir,
FOLDER : var.folder,
AUTO_INSTALL_EXTENSIONS : var.auto_install_extensions,
+8 -2
View File
@@ -16,10 +16,16 @@ if [ -n "${SERVER_BASE_PATH}" ]; then
SERVER_BASE_PATH_ARG="--server-base-path=${SERVER_BASE_PATH}"
fi
# Set disable workspace trust
DISABLE_TRUST_ARG=""
if [ "${DISABLE_TRUST}" = true ]; then
DISABLE_TRUST_ARG="--disable-workspace-trust"
fi
run_vscode_web() {
echo "👷 Running $VSCODE_WEB serve-local $EXTENSION_ARG $SERVER_BASE_PATH_ARG --port ${PORT} --host 127.0.0.1 --accept-server-license-terms --without-connection-token --telemetry-level ${TELEMETRY_LEVEL} in the background..."
echo "👷 Running $VSCODE_WEB serve-local $EXTENSION_ARG $SERVER_BASE_PATH_ARG $DISABLE_TRUST_ARG --port ${PORT} --host 127.0.0.1 --accept-server-license-terms --without-connection-token --telemetry-level ${TELEMETRY_LEVEL} in the background..."
echo "Check logs at ${LOG_PATH}!"
"$VSCODE_WEB" serve-local "$EXTENSION_ARG" "$SERVER_BASE_PATH_ARG" --port "${PORT}" --host 127.0.0.1 --accept-server-license-terms --without-connection-token --telemetry-level "${TELEMETRY_LEVEL}" > "${LOG_PATH}" 2>&1 &
"$VSCODE_WEB" serve-local "$EXTENSION_ARG" "$SERVER_BASE_PATH_ARG" "$DISABLE_TRUST_ARG" --port "${PORT}" --host 127.0.0.1 --accept-server-license-terms --without-connection-token --telemetry-level "${TELEMETRY_LEVEL}" > "${LOG_PATH}" 2>&1 &
}
# Check if the settings file exists...