Compare commits

...

4 Commits

Author SHA1 Message Date
DevCats dd412fbf34 fix(claude-code): change mcp add command to use mcp add-json instead (#564)
## Description

changes the `claude-code mcp add` command to `claude-code mcp add-json`
instead, and updates usage examples with real world mcp server example.

<!-- Briefly describe what this PR does and why -->

## Type of Change

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

## Module Information

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

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

## Testing & Validation

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

## Related Issues
#562 
<!-- Link related issues or write "None" if not applicable -->
2025-11-25 11:44:12 -06:00
dependabot[bot] faff2be207 chore(deps): bump actions/checkout from 5 to 6 in the github-actions group (#561)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 07:47:55 +00:00
Shahar Zrihen 6acded53f6 added positron desktop ide module based on vs code desktop (#528)
Co-authored-by: DevCats <chris@dualriver.com>
Co-authored-by: Atif Ali <atif@coder.com>
2025-11-23 09:19:09 +00:00
35C4n0r f3c24af1db fix(coder/modules/claude-code): check existing session-ids when using --session-id flag (#557) 2025-11-21 14:54:01 +05:30
15 changed files with 268 additions and 27 deletions
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Run check.sh
run: |
+3 -3
View File
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Detect changed files
uses: dorny/paths-filter@v3
id: filter
@@ -69,7 +69,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Install Bun
uses: oven-sh/setup-bun@v2
with:
@@ -93,7 +93,7 @@ jobs:
needs: validate-style
steps:
- name: Check out code
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Authenticate with Google Cloud
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093
with:
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions/setup-go@v6
with:
go-version: stable
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
issues: write
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 440 440">
<g id="Layer_6" data-name="Layer 6">
<rect x="12" y="12" width="416" height="416" rx="103" ry="103" fill="#3a78b1" stroke-width="0"/>
</g>
<g id="Layer_8" data-name="Layer 8">
<path d="M83.6,373.6v-178.6c0-63,52.4-143.7,142.7-143.7s144.1,69.7,144.1,141.1c0,122.6-116.6,143.1-116.6,143.1,0,0,7.2-11.5,7.2-29.5s-8-28-8-28c0,0,60-16,60-87s-53-83-86-83c-57,0-88.3,48.5-88.3,84.3v181.9c0,25.9-17.5,23.9-17.5,23.9h-19.2s-18.4,0-18.4-24.4Z" fill="#fff" stroke-width="0"/>
</g>
<g id="Layer_9" data-name="Layer 9">
<circle cx="204.9" cy="306.6" r="48" fill="#fff" stroke-width="0"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 727 B

+12 -10
View File
@@ -13,7 +13,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 = "4.2.0"
version = "4.2.2"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
@@ -51,7 +51,7 @@ module "claude-code" {
boundary_log_level = "WARN"
boundary_additional_allowed_urls = ["GET *google.com"]
boundary_proxy_port = "8087"
version = "3.4.3"
version = "4.2.2"
}
```
@@ -70,7 +70,7 @@ data "coder_parameter" "ai_prompt" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.0"
version = "4.2.2"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
@@ -89,9 +89,11 @@ module "claude-code" {
mcp = <<-EOF
{
"mcpServers": {
"my-custom-tool": {
"command": "my-tool-server"
"args": ["--port", "8080"]
"memory": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"],
"env": {}
}
}
}
@@ -106,7 +108,7 @@ Run and configure Claude Code as a standalone CLI in your workspace.
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.0"
version = "4.2.2"
agent_id = coder_agent.example.id
workdir = "/home/coder"
install_claude_code = true
@@ -129,7 +131,7 @@ variable "claude_code_oauth_token" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.0"
version = "4.2.2"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token
@@ -202,7 +204,7 @@ resource "coder_env" "bedrock_api_key" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.0"
version = "4.2.2"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
@@ -259,7 +261,7 @@ resource "coder_env" "google_application_credentials" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.0"
version = "4.2.2"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514"
@@ -68,13 +68,16 @@ function setup_claude_configurations() {
mkdir -p "$module_path"
if [ "$ARG_MCP" != "" ]; then
while IFS= read -r server_name && IFS= read -r server_json; do
echo "------------------------"
echo "Executing: claude mcp add \"$server_name\" '$server_json'"
claude mcp add "$server_name" "$server_json"
echo "------------------------"
echo ""
done < <(echo "$ARG_MCP" | jq -r '.mcpServers | to_entries[] | .key, (.value | @json)')
(
cd "$ARG_WORKDIR"
while IFS= read -r server_name && IFS= read -r server_json; do
echo "------------------------"
echo "Executing: claude mcp add-json \"$server_name\" '$server_json' (in $ARG_WORKDIR)"
claude mcp add-json "$server_name" "$server_json"
echo "------------------------"
echo ""
done < <(echo "$ARG_MCP" | jq -r '.mcpServers | to_entries[] | .key, (.value | @json)')
)
fi
if [ -n "$ARG_ALLOWED_TOOLS" ]; then
@@ -103,9 +103,13 @@ task_session_exists() {
local workdir_normalized=$(echo "$ARG_WORKDIR" | tr '/' '-')
local project_dir="$HOME/.claude/projects/${workdir_normalized}"
printf "PROJECT_DIR: %s, workdir_normalized: %s\n" "$project_dir" "$workdir_normalized"
if [ -d "$project_dir" ] && find "$project_dir" -type f -name "*${TASK_SESSION_ID}*" 2> /dev/null | grep -q .; then
printf "TASK_SESSION_ID: %s file found\n" "$TASK_SESSION_ID"
return 0
else
printf "TASK_SESSION_ID: %s file not found\n" "$TASK_SESSION_ID"
return 1
fi
}
@@ -149,7 +153,11 @@ function start_agentapi() {
else
echo "No existing session found"
if [ "$ARG_REPORT_TASKS" = "true" ]; then
ARGS+=(--session-id "$TASK_SESSION_ID")
if task_session_exists; then
ARGS+=(--resume "$TASK_SESSION_ID")
else
ARGS+=(--session-id "$TASK_SESSION_ID")
fi
fi
if [ -n "$ARG_AI_PROMPT" ]; then
if [ "$ARG_REPORT_TASKS" = "true" ]; then
@@ -171,7 +179,11 @@ function start_agentapi() {
else
echo "Continue disabled, starting fresh session"
if [ "$ARG_REPORT_TASKS" = "true" ]; then
ARGS+=(--session-id "$TASK_SESSION_ID")
if task_session_exists; then
ARGS+=(--resume "$TASK_SESSION_ID")
else
ARGS+=(--session-id "$TASK_SESSION_ID")
fi
fi
if [ -n "$ARG_AI_PROMPT" ]; then
if [ "$ARG_REPORT_TASKS" = "true" ]; then
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+12
View File
@@ -0,0 +1,12 @@
---
display_name: "CytoShahar"
bio: "Data engineer by day, maker by night"
avatar: "./.images/avatar.jpeg"
github: "https://github.com/CytoShahar"
linkedin: "https://www.linkedin.com/in/shaharzrihen" # Optional
status: "community"
---
# Shahar Zrihen
Data engineer by day, maker by night
@@ -0,0 +1,38 @@
---
display_name: Positron Desktop
description: Add a one-click button to launch Positron Desktop
icon: ../../../../.icons/positron.svg
verified: true
tags: [ide, positron]
---
# Positron Desktop
Add a button to open any workspace with a single click.
Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder).
```tf
module "positron" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/cytoshahar/positron/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
}
```
## Examples
### Open in a specific directory
```tf
module "positron" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/cytoshahar/positron/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
}
```
Based on the [Coder VS Code Desktop Module](https://github.com/coder/registry/tree/main/registry/coder/modules/vscode-desktop)
@@ -0,0 +1,88 @@
import { describe, expect, it } from "bun:test";
import {
runTerraformApply,
runTerraformInit,
testRequiredVariables,
} from "~test";
describe("positron-desktop", async () => {
await runTerraformInit(import.meta.dir);
testRequiredVariables(import.meta.dir, {
agent_id: "foo",
});
it("default output", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
});
expect(state.outputs.positron_url.value).toBe(
"positron://coder.coder-remote/open?owner=default&workspace=default&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
const coder_app = state.resources.find(
(res) => res.type === "coder_app" && res.name === "positron",
);
expect(coder_app).not.toBeNull();
expect(coder_app?.instances.length).toBe(1);
expect(coder_app?.instances[0].attributes.order).toBeNull();
});
it("adds folder", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
});
expect(state.outputs.positron_url.value).toBe(
"positron://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});
it("adds folder and open_recent", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
open_recent: "true",
});
expect(state.outputs.positron_url.value).toBe(
"positron://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});
it("adds folder but not open_recent", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
openRecent: "false",
});
expect(state.outputs.positron_url.value).toBe(
"positron://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});
it("adds open_recent", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
open_recent: "true",
});
expect(state.outputs.positron_url.value).toBe(
"positron://coder.coder-remote/open?owner=default&workspace=default&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});
it("expect order to be set", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
order: "22",
});
const coder_app = state.resources.find(
(res) => res.type === "coder_app" && res.name === "positron",
);
expect(coder_app).not.toBeNull();
expect(coder_app?.instances.length).toBe(1);
expect(coder_app?.instances[0].attributes.order).toBe(22);
});
});
@@ -0,0 +1,74 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.5"
}
}
}
locals {
icon_url = "/icon/positron.svg"
}
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
variable "folder" {
type = string
description = "The folder to open in Positron."
default = ""
}
variable "open_recent" {
type = bool
description = "Open the most recent workspace or folder. Falls back to the folder if there is no recent workspace or folder to open."
default = false
}
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
}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
resource "coder_app" "positron" {
agent_id = var.agent_id
external = true
icon = local.icon_url
slug = "positron"
display_name = "Positron Desktop"
order = var.order
group = var.group
url = join("", [
"positron://coder.coder-remote/open",
"?owner=",
data.coder_workspace_owner.me.name,
"&workspace=",
data.coder_workspace.me.name,
var.folder != "" ? join("", ["&folder=", var.folder]) : "",
var.open_recent ? "&openRecent" : "",
"&url=",
data.coder_workspace.me.access_url,
"&token=$SESSION_TOKEN",
])
}
output "positron_url" {
value = coder_app.positron.url
description = "Positron Desktop URL."
}