diff --git a/.icons/aider.svg b/.icons/aider.svg
index 44e064ff..bb54e1fb 100644
--- a/.icons/aider.svg
+++ b/.icons/aider.svg
@@ -1,3 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/airflow.svg b/.icons/airflow.svg
index 06b18bee..29a32e08 100644
--- a/.icons/airflow.svg
+++ b/.icons/airflow.svg
@@ -1,18 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/akamai.svg b/.icons/akamai.svg
index 4af3fe08..d5b778bc 100644
--- a/.icons/akamai.svg
+++ b/.icons/akamai.svg
@@ -1,4 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/amazon-q.svg b/.icons/amazon-q.svg
index 4a9b3262..1c217bdc 100644
--- a/.icons/amazon-q.svg
+++ b/.icons/amazon-q.svg
@@ -1,13 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/antigravity.svg b/.icons/antigravity.svg
index a046b6b6..54eec2c4 100644
--- a/.icons/antigravity.svg
+++ b/.icons/antigravity.svg
@@ -1,2 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/auggie.svg b/.icons/auggie.svg
index 590bd5aa..180e1dee 100644
--- a/.icons/auggie.svg
+++ b/.icons/auggie.svg
@@ -1,8 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/auto-dev-server.svg b/.icons/auto-dev-server.svg
index f043b56d..aae4a1a5 100644
--- a/.icons/auto-dev-server.svg
+++ b/.icons/auto-dev-server.svg
@@ -1,4 +1 @@
-
-
+
\ No newline at end of file
diff --git a/.icons/aws.svg b/.icons/aws.svg
index 3244c974..828a8223 100644
--- a/.icons/aws.svg
+++ b/.icons/aws.svg
@@ -1,13 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/azure.svg b/.icons/azure.svg
index 645ac663..5e96a718 100644
--- a/.icons/azure.svg
+++ b/.icons/azure.svg
@@ -1,23 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/box-emoji.svg b/.icons/box-emoji.svg
index a2595599..dc8ed58c 100644
--- a/.icons/box-emoji.svg
+++ b/.icons/box-emoji.svg
@@ -1,27 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/claude.svg b/.icons/claude.svg
index 998fb0d5..22dcf464 100644
--- a/.icons/claude.svg
+++ b/.icons/claude.svg
@@ -1,4 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/cloud-devops.svg b/.icons/cloud-devops.svg
index a3b6e82a..659dcc52 100644
--- a/.icons/cloud-devops.svg
+++ b/.icons/cloud-devops.svg
@@ -1,29 +1 @@
-
-
+
\ No newline at end of file
diff --git a/.icons/cmux.svg b/.icons/cmux.svg
index 95b56bb0..012bff21 100644
--- a/.icons/cmux.svg
+++ b/.icons/cmux.svg
@@ -1,47 +1 @@
-
-
+
\ No newline at end of file
diff --git a/.icons/coder.svg b/.icons/coder.svg
index 60d7eff6..5db19df6 100644
--- a/.icons/coder.svg
+++ b/.icons/coder.svg
@@ -1,4 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/copyparty.svg b/.icons/copyparty.svg
index 2c4f0d04..bb8cf89e 100644
--- a/.icons/copyparty.svg
+++ b/.icons/copyparty.svg
@@ -1,210 +1 @@
-
-
+
\ No newline at end of file
diff --git a/.icons/desktop.svg b/.icons/desktop.svg
index 77d231ce..2dee883a 100644
--- a/.icons/desktop.svg
+++ b/.icons/desktop.svg
@@ -1,5 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/devcontainers.svg b/.icons/devcontainers.svg
index fb0443bd..72c2fc32 100644
--- a/.icons/devcontainers.svg
+++ b/.icons/devcontainers.svg
@@ -1,2 +1 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/digital-ocean.svg b/.icons/digital-ocean.svg
index 6f10b237..7a3e8462 100644
--- a/.icons/digital-ocean.svg
+++ b/.icons/digital-ocean.svg
@@ -1,10 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/docker.svg b/.icons/docker.svg
index 78e549ef..2a174a89 100644
--- a/.icons/docker.svg
+++ b/.icons/docker.svg
@@ -1,3 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/dotfiles.svg b/.icons/dotfiles.svg
index c57ef859..bc582549 100644
--- a/.icons/dotfiles.svg
+++ b/.icons/dotfiles.svg
@@ -1,10 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/electric-plug-emoji.svg b/.icons/electric-plug-emoji.svg
index 15743822..d8b03f26 100644
--- a/.icons/electric-plug-emoji.svg
+++ b/.icons/electric-plug-emoji.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/exoscale.svg b/.icons/exoscale.svg
index c56a6154..dc4f8462 100644
--- a/.icons/exoscale.svg
+++ b/.icons/exoscale.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/filebrowser.svg b/.icons/filebrowser.svg
index 5e78eccf..42bcc8db 100644
--- a/.icons/filebrowser.svg
+++ b/.icons/filebrowser.svg
@@ -1,147 +1 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/fleet.svg b/.icons/fleet.svg
index ba910eb9..494c2406 100644
--- a/.icons/fleet.svg
+++ b/.icons/fleet.svg
@@ -1,60 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/folder.svg b/.icons/folder.svg
index b718dea5..1759bfc4 100644
--- a/.icons/folder.svg
+++ b/.icons/folder.svg
@@ -1 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/gateway.svg b/.icons/gateway.svg
index b68e9490..238b4dda 100644
--- a/.icons/gateway.svg
+++ b/.icons/gateway.svg
@@ -1,64 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/gemini.svg b/.icons/gemini.svg
index f1cf3575..33a088c1 100644
--- a/.icons/gemini.svg
+++ b/.icons/gemini.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/git.svg b/.icons/git.svg
index ceef1163..831d3100 100644
--- a/.icons/git.svg
+++ b/.icons/git.svg
@@ -1,3 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/goose.svg b/.icons/goose.svg
index cbbe8419..e73ebc35 100644
--- a/.icons/goose.svg
+++ b/.icons/goose.svg
@@ -1,4 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/hetzner.svg b/.icons/hetzner.svg
index 74bb87c1..24ac242e 100644
--- a/.icons/hetzner.svg
+++ b/.icons/hetzner.svg
@@ -1,5 +1 @@
-
-
+
\ No newline at end of file
diff --git a/.icons/jetbrains.svg b/.icons/jetbrains.svg
index b281f962..8a0d73e9 100644
--- a/.icons/jetbrains.svg
+++ b/.icons/jetbrains.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/jfrog.svg b/.icons/jfrog.svg
index e137700c..ad0050a3 100644
--- a/.icons/jfrog.svg
+++ b/.icons/jfrog.svg
@@ -1,3 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/jupyter.svg b/.icons/jupyter.svg
index 38350dfe..9beddb14 100644
--- a/.icons/jupyter.svg
+++ b/.icons/jupyter.svg
@@ -1,14 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/kasmvnc.svg b/.icons/kasmvnc.svg
index 958f2832..33d25197 100644
--- a/.icons/kasmvnc.svg
+++ b/.icons/kasmvnc.svg
@@ -1,7 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/kiro.svg b/.icons/kiro.svg
index e132ead1..823400d2 100644
--- a/.icons/kiro.svg
+++ b/.icons/kiro.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/kubernetes.svg b/.icons/kubernetes.svg
index 42bb9229..7f07a7f5 100644
--- a/.icons/kubernetes.svg
+++ b/.icons/kubernetes.svg
@@ -1,4 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/lxc.svg b/.icons/lxc.svg
index 0e8e118f..13cd5c59 100644
--- a/.icons/lxc.svg
+++ b/.icons/lxc.svg
@@ -1,21 +1 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/mux.svg b/.icons/mux.svg
index a6ce26f1..3ee3276d 100644
--- a/.icons/mux.svg
+++ b/.icons/mux.svg
@@ -1,5 +1 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/nextflow.svg b/.icons/nextflow.svg
index bcc10553..e9a38072 100644
--- a/.icons/nextflow.svg
+++ b/.icons/nextflow.svg
@@ -1,6 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/nexus-repository.svg b/.icons/nexus-repository.svg
index ca135cd5..45a2bf60 100644
--- a/.icons/nexus-repository.svg
+++ b/.icons/nexus-repository.svg
@@ -1,5 +1 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/nomad.svg b/.icons/nomad.svg
index b4dc91c7..ff7d00d0 100644
--- a/.icons/nomad.svg
+++ b/.icons/nomad.svg
@@ -1,2 +1 @@
-
-
+
\ No newline at end of file
diff --git a/.icons/openai.svg b/.icons/openai.svg
index ba36fc2a..ffa802de 100644
--- a/.icons/openai.svg
+++ b/.icons/openai.svg
@@ -1,15 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/opencode.svg b/.icons/opencode.svg
index b79c7332..790fbc49 100644
--- a/.icons/opencode.svg
+++ b/.icons/opencode.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/openwebui.svg b/.icons/openwebui.svg
index 06f67645..b4ac79b0 100644
--- a/.icons/openwebui.svg
+++ b/.icons/openwebui.svg
@@ -1,5 +1 @@
-
-
+
\ No newline at end of file
diff --git a/.icons/perplexica.svg b/.icons/perplexica.svg
index 0b3a85b5..e1e0226f 100644
--- a/.icons/perplexica.svg
+++ b/.icons/perplexica.svg
@@ -1,8 +1 @@
-
-
+
\ No newline at end of file
diff --git a/.icons/personalize.svg b/.icons/personalize.svg
index 76bc6780..3e402bc1 100644
--- a/.icons/personalize.svg
+++ b/.icons/personalize.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/pgadmin.svg b/.icons/pgadmin.svg
index 9fa5c4d5..f75dee65 100644
--- a/.icons/pgadmin.svg
+++ b/.icons/pgadmin.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/positron.svg b/.icons/positron.svg
index 590372e4..6d1282f5 100644
--- a/.icons/positron.svg
+++ b/.icons/positron.svg
@@ -1,12 +1 @@
-
-
+
\ No newline at end of file
diff --git a/.icons/proxmox.svg b/.icons/proxmox.svg
index c18256e2..5758f8c5 100755
--- a/.icons/proxmox.svg
+++ b/.icons/proxmox.svg
@@ -1,137 +1 @@
-
-
+
\ No newline at end of file
diff --git a/.icons/rdp.svg b/.icons/rdp.svg
index a6722326..1e83ccdf 100644
--- a/.icons/rdp.svg
+++ b/.icons/rdp.svg
@@ -1,35 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/rustdesk.svg b/.icons/rustdesk.svg
index 6c801233..fdc4e04c 100644
--- a/.icons/rustdesk.svg
+++ b/.icons/rustdesk.svg
@@ -1,5 +1 @@
-
-
+
\ No newline at end of file
diff --git a/.icons/scaleway.svg b/.icons/scaleway.svg
index ebe1ddf2..488c90c1 100644
--- a/.icons/scaleway.svg
+++ b/.icons/scaleway.svg
@@ -1,2 +1 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/slack.svg b/.icons/slack.svg
index fb55f724..1f9731dd 100644
--- a/.icons/slack.svg
+++ b/.icons/slack.svg
@@ -1,6 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/sourcegraph-amp.svg b/.icons/sourcegraph-amp.svg
index 83777bd2..5466803a 100644
--- a/.icons/sourcegraph-amp.svg
+++ b/.icons/sourcegraph-amp.svg
@@ -1,5 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/tasks.svg b/.icons/tasks.svg
index 67088c42..0ab7ea7f 100644
--- a/.icons/tasks.svg
+++ b/.icons/tasks.svg
@@ -1,5 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/vault.svg b/.icons/vault.svg
index c90525cd..d9c882a1 100644
--- a/.icons/vault.svg
+++ b/.icons/vault.svg
@@ -1,2 +1 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.icons/vsphere.svg b/.icons/vsphere.svg
new file mode 100644
index 00000000..dd94f13d
--- /dev/null
+++ b/.icons/vsphere.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/.icons/windsurf.svg b/.icons/windsurf.svg
index 2e4e4e49..f8164466 100644
--- a/.icons/windsurf.svg
+++ b/.icons/windsurf.svg
@@ -1,3 +1 @@
-
+
\ No newline at end of file
diff --git a/.icons/zed.svg b/.icons/zed.svg
index 06b5c183..d5910ecc 100644
--- a/.icons/zed.svg
+++ b/.icons/zed.svg
@@ -1,3 +1 @@
-
+
\ No newline at end of file
diff --git a/cmd/.icons/docker.svg b/cmd/.icons/docker.svg
new file mode 100644
index 00000000..2a174a89
--- /dev/null
+++ b/cmd/.icons/docker.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/cmd/.icons/goose.svg b/cmd/.icons/goose.svg
new file mode 100644
index 00000000..e73ebc35
--- /dev/null
+++ b/cmd/.icons/goose.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/cmd/readmevalidation/codermodules_test.go b/cmd/readmevalidation/codermodules_test.go
index 194a861e..e9e88bfc 100644
--- a/cmd/readmevalidation/codermodules_test.go
+++ b/cmd/readmevalidation/codermodules_test.go
@@ -1,22 +1,117 @@
package main
import (
- _ "embed"
+ "os"
+ "path/filepath"
"testing"
)
-//go:embed testSamples/sampleReadmeBody.md
-var testBody string
+type readmeTestCase struct {
+ filePath string
+ shouldPass bool
+}
-func TestValidateCoderResourceReadmeBody(t *testing.T) {
+func loadTestCases(t *testing.T, dir string, shouldPass bool) []readmeTestCase {
+ t.Helper()
+ files, err := os.ReadDir(dir)
+ if err != nil {
+ t.Fatalf("Failed to read directory %s: %v", dir, err)
+ }
+
+ var testCases []readmeTestCase
+ for _, file := range files {
+ testCases = append(testCases, readmeTestCase{
+ filePath: filepath.Join(dir, file.Name()),
+ shouldPass: shouldPass,
+ })
+ }
+ return testCases
+}
+
+func TestValidateModuleReadmes(t *testing.T) {
t.Parallel()
- t.Run("Parses a valid README body with zero issues", func(t *testing.T) {
- t.Parallel()
+ testCases := append(
+ loadTestCases(t, "testSamples/modules/pass", true),
+ loadTestCases(t, "testSamples/modules/fail", false)...,
+ )
- errs := validateCoderModuleReadmeBody(testBody)
- for _, e := range errs {
- t.Error(e)
- }
- })
+ for _, tc := range testCases {
+ t.Run(tc.filePath, func(t *testing.T) {
+ t.Parallel()
+
+ content, err := os.ReadFile(tc.filePath)
+ if err != nil {
+ t.Fatalf("Failed to read file: %v", err)
+ }
+
+ rm := readme{
+ filePath: tc.filePath,
+ rawText: string(content),
+ }
+
+ resource, errs := parseCoderResourceReadme("modules", rm)
+ if len(errs) != 0 {
+ if tc.shouldPass {
+ for _, e := range errs {
+ t.Errorf("Unexpected parsing error: %v", e)
+ }
+ }
+ return
+ }
+
+ validationErrs := validateCoderModuleReadme(resource)
+ if tc.shouldPass && len(validationErrs) != 0 {
+ for _, e := range validationErrs {
+ t.Errorf("Unexpected validation error: %v", e)
+ }
+ } else if !tc.shouldPass && len(validationErrs) == 0 {
+ t.Error("Expected validation errors but got none")
+ }
+ })
+ }
+}
+
+func TestValidateTemplateReadmes(t *testing.T) {
+ t.Parallel()
+
+ testCases := append(
+ loadTestCases(t, "testSamples/templates/pass", true),
+ loadTestCases(t, "testSamples/templates/fail", false)...,
+ )
+
+ for _, tc := range testCases {
+ t.Run(tc.filePath, func(t *testing.T) {
+ t.Parallel()
+
+ content, err := os.ReadFile(tc.filePath)
+ if err != nil {
+ t.Fatalf("Failed to read file: %v", err)
+ }
+
+ rm := readme{
+ filePath: tc.filePath,
+ rawText: string(content),
+ }
+
+ resource, errs := parseCoderResourceReadme("templates", rm)
+ if len(errs) != 0 {
+ if tc.shouldPass {
+ for _, e := range errs {
+ t.Errorf("Unexpected parsing error: %v", e)
+ }
+ }
+ return
+ }
+
+ validationErrs := validateCoderModuleReadme(resource)
+ if tc.shouldPass && len(validationErrs) != 0 {
+ for _, e := range validationErrs {
+ t.Errorf("Unexpected validation error: %v", e)
+ }
+ } else if !tc.shouldPass && len(validationErrs) == 0 {
+ t.Error("Expected validation errors but got none")
+ }
+ })
+ }
}
diff --git a/cmd/readmevalidation/coderresources.go b/cmd/readmevalidation/coderresources.go
index 818d73c8..ee723265 100644
--- a/cmd/readmevalidation/coderresources.go
+++ b/cmd/readmevalidation/coderresources.go
@@ -82,33 +82,43 @@ func validateCoderResourceDescription(description string) error {
return nil
}
-func isPermittedRelativeURL(checkURL string) bool {
- // Would normally be skittish about having relative paths like this, but it should be safe because we have
- // guarantees about the structure of the repo, and where this logic will run.
- return strings.HasPrefix(checkURL, "./") || strings.HasPrefix(checkURL, "/") || strings.HasPrefix(checkURL, "../../../../.icons")
+func isPermittedRelativeURL(checkURL string, readmeFilePath string) error {
+ // Icon URLs must reference the top-level .icons directory
+ expectedPrefix := "../../../../.icons/"
+ if !strings.HasPrefix(checkURL, expectedPrefix) {
+ return xerrors.Errorf("icon URL %q must reference the top-level .icons directory using %q", checkURL, expectedPrefix)
+ }
+
+ // Resolve the path relative to the README file and check if it exists
+ readmeDir := path.Dir(readmeFilePath)
+ resolvedPath := path.Join(readmeDir, checkURL)
+
+ if _, err := os.Stat(resolvedPath); err != nil {
+ if os.IsNotExist(err) {
+ return xerrors.Errorf("icon file does not exist at resolved path %q (referenced as %q)", resolvedPath, checkURL)
+ }
+ return xerrors.Errorf("error checking icon file at %q: %v", resolvedPath, err)
+ }
+
+ return nil
}
-func validateCoderResourceIconURL(iconURL string) []error {
+func validateCoderResourceIconURL(iconURL string, filePath string) []error {
if iconURL == "" {
return []error{xerrors.New("icon URL cannot be empty")}
}
var errs []error
- // If the URL does not have a relative path.
- if !strings.HasPrefix(iconURL, ".") && !strings.HasPrefix(iconURL, "/") {
- if _, err := url.ParseRequestURI(iconURL); err != nil {
- errs = append(errs, xerrors.New("absolute icon URL is not correctly formatted"))
- }
- if strings.Contains(iconURL, "?") {
- errs = append(errs, xerrors.New("icon URLs cannot contain query parameters"))
- }
+ // Reject absolute HTTP/HTTPS URLs - all icons must be local to the repository
+ if strings.HasPrefix(iconURL, "http://") || strings.HasPrefix(iconURL, "https://") {
+ errs = append(errs, xerrors.Errorf("icon URL must reference the top-level .icons directory, not an absolute URL %q", iconURL))
return errs
}
- // If the URL has a relative path.
- if !isPermittedRelativeURL(iconURL) {
- errs = append(errs, xerrors.Errorf("relative icon URL %q must either be scoped to that module's directory, or the top-level /.icons directory (this can usually be done by starting the path with \"../../../.icons\")", iconURL))
+ // Validate that the icon references ../../../../.icons/ and exists
+ if err := isPermittedRelativeURL(iconURL, filePath); err != nil {
+ errs = append(errs, err)
}
return errs
@@ -153,7 +163,7 @@ func validateCoderResourceFrontmatter(resourceType string, filePath string, fm c
errs = append(errs, addFilePathToError(filePath, err))
}
- for _, err := range validateCoderResourceIconURL(fm.IconURL) {
+ for _, err := range validateCoderResourceIconURL(fm.IconURL, filePath) {
errs = append(errs, addFilePathToError(filePath, err))
}
for _, err := range validateSupportedOperatingSystems(fm.OperatingSystems) {
diff --git a/cmd/readmevalidation/testSamples/modules/fail/absoluteIconPath.md b/cmd/readmevalidation/testSamples/modules/fail/absoluteIconPath.md
new file mode 100644
index 00000000..d68264b0
--- /dev/null
+++ b/cmd/readmevalidation/testSamples/modules/fail/absoluteIconPath.md
@@ -0,0 +1,22 @@
+---
+display_name: "Goose"
+description: "Run the Goose agent in your workspace to generate code and perform tasks"
+icon: "https://github.com/coder/registry/pull/599.svg"
+verified: false
+tags: ["ai", "agent"]
+---
+
+# Goose
+
+Run the [Goose](https://block.github.io/goose/) agent in your workspace to generate code and perform tasks.
+
+```tf
+module "goose" {
+ source = "registry.coder.com/coder/goose/coder"
+ version = "1.0.31"
+ agent_id = coder_agent.main.id
+ folder = "/home/coder"
+ install_goose = true
+ goose_version = "v1.0.16"
+}
+```
diff --git a/cmd/readmevalidation/testSamples/modules/fail/wrongPathFormat.md b/cmd/readmevalidation/testSamples/modules/fail/wrongPathFormat.md
new file mode 100644
index 00000000..e9c671c4
--- /dev/null
+++ b/cmd/readmevalidation/testSamples/modules/fail/wrongPathFormat.md
@@ -0,0 +1,19 @@
+---
+display_name: "Wrong Path"
+description: "Test module with wrong icon path format"
+icon: "../../../../.icons/invalid.svg"
+verified: false
+tags: ["test"]
+---
+
+# Wrong Path
+
+This should fail validation.
+
+```tf
+module "test" {
+ source = "registry.coder.com/coder/test/coder"
+ version = "1.0.0"
+ agent_id = coder_agent.main.id
+}
+```
diff --git a/cmd/readmevalidation/testSamples/modules/pass/sampleModuleReadme.md b/cmd/readmevalidation/testSamples/modules/pass/sampleModuleReadme.md
new file mode 100644
index 00000000..b0db1f26
--- /dev/null
+++ b/cmd/readmevalidation/testSamples/modules/pass/sampleModuleReadme.md
@@ -0,0 +1,56 @@
+---
+display_name: "Docker Container"
+description: "Develop in a container on a Docker host"
+icon: "../../../../.icons/docker.svg"
+verified: true
+tags: ["docker", "container"]
+supported_os: ["linux", "macos"]
+---
+
+# Docker Container
+
+Develop in a Docker container on a remote Docker host.
+
+```tf
+terraform {
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ version = "~> 1.0"
+ }
+ docker = {
+ source = "kreuzwerker/docker"
+ version = "~> 3.0"
+ }
+ }
+}
+
+provider "docker" {}
+
+provider "coder" {}
+
+data "coder_workspace" "me" {}
+
+resource "coder_agent" "main" {
+ os = "linux"
+ arch = "amd64"
+}
+
+resource "docker_container" "workspace" {
+ image = "codercom/enterprise-base:ubuntu"
+ name = "coder-${data.coder_workspace.me.owner}-${data.coder_workspace.me.name}"
+
+ env = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"]
+}
+```
+
+## Getting Started
+
+This template creates a Docker container on your Docker host. You'll need:
+
+- A Docker host accessible from your Coder deployment
+- The Docker provider configured with appropriate credentials
+
+## Customization
+
+You can customize the container image, resources, and configuration to match your needs.
diff --git a/cmd/readmevalidation/testSamples/sampleReadmeBody.md b/cmd/readmevalidation/testSamples/sampleReadmeBody.md
deleted file mode 100644
index b96662af..00000000
--- a/cmd/readmevalidation/testSamples/sampleReadmeBody.md
+++ /dev/null
@@ -1,121 +0,0 @@
-# Goose
-
-Run the [Goose](https://block.github.io/goose/) agent in your workspace to generate code and perform tasks.
-
-```tf
-module "goose" {
- source = "registry.coder.com/coder/goose/coder"
- version = "1.0.31"
- agent_id = coder_agent.main.id
- folder = "/home/coder"
- install_goose = true
- goose_version = "v1.0.16"
-}
-```
-
-## Prerequisites
-
-- `screen` must be installed in your workspace to run Goose in the background
-- You must add the [Coder Login](https://registry.coder.com/modules/coder-login) module to your template
-
-The `codercom/oss-dogfood:latest` container image can be used for testing on container-based workspaces.
-
-## Examples
-
-Your workspace must have `screen` installed to use this.
-
-### Run in the background and report tasks (Experimental)
-
-> This functionality is in early access as of Coder v2.21 and is still evolving.
-> For now, we recommend testing it in a demo or staging environment,
-> rather than deploying to production
->
-> Learn more in [the Coder documentation](https://coder.com/docs/tutorials/ai-agents)
->
-> Join our [Discord channel](https://discord.gg/coder) or
-> [contact us](https://coder.com/contact) to get help or share feedback.
-
-```tf
-module "coder-login" {
- count = data.coder_workspace.me.start_count
- source = "registry.coder.com/coder/coder-login/coder"
- version = "1.0.15"
- agent_id = coder_agent.main.id
-}
-
-variable "anthropic_api_key" {
- type = string
- description = "The Anthropic API key"
- sensitive = true
-}
-
-data "coder_parameter" "ai_prompt" {
- type = "string"
- name = "AI Prompt"
- default = ""
- description = "Write a prompt for Goose"
- mutable = true
-}
-
-# Set the prompt and system prompt for Goose via environment variables
-resource "coder_agent" "main" {
- # ...
- env = {
- GOOSE_SYSTEM_PROMPT = <<-EOT
- You are a helpful assistant that can help write code.
-
- Run all long running tasks (e.g. npm run dev) in the background and not in the foreground.
-
- Periodically check in on background tasks.
-
- Notify Coder of the status of the task before and after your steps.
- EOT
- GOOSE_TASK_PROMPT = data.coder_parameter.ai_prompt.value
-
- # An API key is required for experiment_auto_configure
- # See https://block.github.io/goose/docs/getting-started/providers
- ANTHROPIC_API_KEY = var.anthropic_api_key # or use a coder_parameter
- }
-}
-
-module "goose" {
- count = data.coder_workspace.me.start_count
- source = "registry.coder.com/coder/goose/coder"
- version = "1.0.31"
- agent_id = coder_agent.main.id
- folder = "/home/coder"
- install_goose = true
- goose_version = "v1.0.16"
-
- # Enable experimental features
- experiment_report_tasks = true
-
- # Run Goose in the background
- experiment_use_screen = true
-
- # Avoid configuring Goose manually
- experiment_auto_configure = true
-
- # Required for experiment_auto_configure
- experiment_goose_provider = "anthropic"
- experiment_goose_model = "claude-3-5-sonnet-latest"
-}
-```
-
-## Run standalone
-
-Run Goose as a standalone app in your workspace. This will install Goose and run it directly without using screen or any task reporting to the Coder UI.
-
-```tf
-module "goose" {
- source = "registry.coder.com/coder/goose/coder"
- version = "1.0.31"
- agent_id = coder_agent.main.id
- folder = "/home/coder"
- install_goose = true
- goose_version = "v1.0.16"
-
- # Icon is not available in Coder v2.20 and below, so we'll use a custom icon URL
- icon = "https://raw.githubusercontent.com/block/goose/refs/heads/main/ui/desktop/src/images/icon.svg"
-}
-```
diff --git a/cmd/readmevalidation/testSamples/templates/fail/absoluteIconPath.md b/cmd/readmevalidation/testSamples/templates/fail/absoluteIconPath.md
new file mode 100644
index 00000000..879a2a83
--- /dev/null
+++ b/cmd/readmevalidation/testSamples/templates/fail/absoluteIconPath.md
@@ -0,0 +1,27 @@
+---
+display_name: "Docker Container"
+description: "Develop in a container on a Docker host"
+icon: "https://github.com/coder/registry/pull/599.jpeg"
+verified: true
+tags: ["docker", "container"]
+supported_os: ["linux", "macos"]
+---
+
+# Docker Container
+
+Develop in a Docker container on a remote Docker host.
+
+```tf
+terraform {
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ version = "~> 1.0"
+ }
+ docker = {
+ source = "kreuzwerker/docker"
+ version = "~> 3.0"
+ }
+ }
+}
+```
diff --git a/cmd/readmevalidation/testSamples/templates/fail/wrongPathFormat.md b/cmd/readmevalidation/testSamples/templates/fail/wrongPathFormat.md
new file mode 100644
index 00000000..7078cfb8
--- /dev/null
+++ b/cmd/readmevalidation/testSamples/templates/fail/wrongPathFormat.md
@@ -0,0 +1,20 @@
+---
+display_name: "Docker Container"
+description: "Develop in a container on a Docker host"
+icon: "../../../../.icons/invalid.svg"
+verified: true
+tags: ["docker", "container"]
+supported_os: ["linux", "macos"]
+---
+
+# Wrong Path
+
+This should fail validation.
+
+```tf
+module "test" {
+ source = "registry.coder.com/coder/test/coder"
+ version = "1.0.0"
+ agent_id = coder_agent.main.id
+}
+```
diff --git a/cmd/readmevalidation/testSamples/templates/pass/sampleTemplateReadme.md b/cmd/readmevalidation/testSamples/templates/pass/sampleTemplateReadme.md
new file mode 100644
index 00000000..b0db1f26
--- /dev/null
+++ b/cmd/readmevalidation/testSamples/templates/pass/sampleTemplateReadme.md
@@ -0,0 +1,56 @@
+---
+display_name: "Docker Container"
+description: "Develop in a container on a Docker host"
+icon: "../../../../.icons/docker.svg"
+verified: true
+tags: ["docker", "container"]
+supported_os: ["linux", "macos"]
+---
+
+# Docker Container
+
+Develop in a Docker container on a remote Docker host.
+
+```tf
+terraform {
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ version = "~> 1.0"
+ }
+ docker = {
+ source = "kreuzwerker/docker"
+ version = "~> 3.0"
+ }
+ }
+}
+
+provider "docker" {}
+
+provider "coder" {}
+
+data "coder_workspace" "me" {}
+
+resource "coder_agent" "main" {
+ os = "linux"
+ arch = "amd64"
+}
+
+resource "docker_container" "workspace" {
+ image = "codercom/enterprise-base:ubuntu"
+ name = "coder-${data.coder_workspace.me.owner}-${data.coder_workspace.me.name}"
+
+ env = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"]
+}
+```
+
+## Getting Started
+
+This template creates a Docker container on your Docker host. You'll need:
+
+- A Docker host accessible from your Coder deployment
+- The Docker provider configured with appropriate credentials
+
+## Customization
+
+You can customize the container image, resources, and configuration to match your needs.
diff --git a/registry/anis/.images/avatar.png b/registry/anis/.images/avatar.png
new file mode 100644
index 00000000..3fb22a10
Binary files /dev/null and b/registry/anis/.images/avatar.png differ
diff --git a/registry/anis/README.md b/registry/anis/README.md
new file mode 100644
index 00000000..20d5e708
--- /dev/null
+++ b/registry/anis/README.md
@@ -0,0 +1,12 @@
+---
+display_name: "Anis Khalfallah"
+bio: "DevOps Engineer"
+github: "aniskhalfallah"
+avatar: "./.images/avatar.png"
+linkedin: "https://www.linkedin.com/in/khalfallah-anis/"
+status: "community"
+---
+
+# Anis KHALFALLAH
+
+DevOps Engineer
diff --git a/registry/anis/templates/vmware-linux/README.md b/registry/anis/templates/vmware-linux/README.md
new file mode 100644
index 00000000..e56957fc
--- /dev/null
+++ b/registry/anis/templates/vmware-linux/README.md
@@ -0,0 +1,81 @@
+---
+display_name: VMware vSphere VM (Linux)
+description: Provision VMware vSphere virtual machines as Coder workspaces
+icon: ../../../../.icons/vsphere.svg
+verified: false
+tags: [vm, linux, vmware, vsphere]
+---
+
+# Summary
+
+Provision VMware vSphere virtual machines as [Coder workspaces](https://coder.com/docs/workspaces) using this Terraform template.
+
+## Prerequisites
+
+To deploy Coder workspaces on VMware vSphere, you'll need the following:
+
+### vSphere Resources
+
+Before deploying, ensure your vSphere environment has:
+
+- A **vSphere Datacenter** already created
+- A **Compute Cluster** within that datacenter
+- A **Datastore** with sufficient storage capacity
+- A **Network** (port group) accessible by VMs
+- A **VM Template** with Ubuntu and cloud-init configured
+
+### VM Template Requirements
+
+Your VM template must have
+
+- **cloud-init** installed and configured for VMware datasource
+
+### vSphere Authentication
+
+You'll need the following credentials:
+
+- **vSphere Server** (hostname or IP)
+- **Username**
+- **Password**
+- **Datacenter Name**
+- **Cluster Name**
+- **Datastore Name**
+- **Network Name**
+- **VM Template Name**
+
+[VMware Provider Documentation](https://registry.terraform.io/providers/hashicorp/vsphere/latest/docs)
+
+---
+
+## Example `.tfvars` File
+
+```hcl
+vsphere_server = "vcenter.example.com"
+vsphere_username = "administrator@vsphere.local"
+vsphere_password = "YourSecurePassword123!"
+vsphere_datacenter = "DC01"
+cluster_name = "Cluster01"
+vsphere_datastore = "datastore1"
+vsphere_network = "VM Network"
+vm_template = "ubuntu-22.04-cloud-init-template"
+```
+
+---
+
+## Architecture
+
+This template creates:
+
+- A **vSphere Virtual Machine** per workspace
+- **Dynamic resource allocation** (CPU, memory configurable by users)
+- **Two disks**: root disk (from template) and separate home volume
+- **Coder agent** installed via cloud-init
+- **code-server** for browser-based VS Code access
+
+## Workspace Parameters
+
+Users can customize their workspace with:
+
+- **VCPUs**: 1, 2, 4, or 8 virtual CPUs
+- **Memory**: 1, 2, 4, 8, 16, or 32 GB RAM
+- **Home Volume Size**: 10-1024 GB (default: 20 GB)
diff --git a/registry/anis/templates/vmware-linux/cloud-init/cloud-config.yaml.tftpl b/registry/anis/templates/vmware-linux/cloud-init/cloud-config.yaml.tftpl
new file mode 100644
index 00000000..5faeab3d
--- /dev/null
+++ b/registry/anis/templates/vmware-linux/cloud-init/cloud-config.yaml.tftpl
@@ -0,0 +1,56 @@
+#cloud-config
+hostname: ${hostname}
+users:
+ - name: ${username}
+ sudo: ["ALL=(ALL) NOPASSWD:ALL"]
+ groups: sudo
+ shell: /bin/bash
+packages:
+ - git
+ - curl
+ - wget
+ - unzip
+disk_setup:
+ /dev/sdb:
+ table_type: "gpt"
+ layout: true
+ overwrite: false
+fs_setup:
+ - label: ${home_volume_label}
+ filesystem: ext4
+ device: /dev/sdb
+ partition: auto
+mounts:
+ - ["/dev/sdb", "/home/${username}", "ext4", "defaults", "0", "2"]
+write_files:
+ - path: /opt/coder/init
+ permissions: "0755"
+ encoding: b64
+ content: ${init_script}
+ - path: /etc/systemd/system/coder-agent.service
+ permissions: "0644"
+ content: |
+ [Unit]
+ Description=Coder Agent
+ After=network-online.target
+ Wants=network-online.target
+
+ [Service]
+ User=${username}
+ ExecStart=/opt/coder/init
+ Environment=CODER_AGENT_TOKEN=${coder_agent_token}
+ Restart=always
+ RestartSec=10
+ TimeoutStopSec=90
+ KillMode=process
+
+ OOMScoreAdjust=-1000
+ SyslogIdentifier=coder-agent
+
+ [Install]
+ WantedBy=multi-user.target
+runcmd:
+ - mkdir -p /home/${username}
+ - chown ${username}:${username} /home/${username}
+ - systemctl enable coder-agent
+ - systemctl start coder-agent
diff --git a/registry/anis/templates/vmware-linux/main.tf b/registry/anis/templates/vmware-linux/main.tf
new file mode 100644
index 00000000..13385493
--- /dev/null
+++ b/registry/anis/templates/vmware-linux/main.tf
@@ -0,0 +1,245 @@
+terraform {
+ required_providers {
+ coder = {
+ source = "coder/coder"
+ }
+ vsphere = {
+ source = "vmware/vsphere"
+ }
+ }
+}
+
+
+provider "vsphere" {
+ user = var.vsphere_username
+ password = var.vsphere_password
+ vsphere_server = var.vsphere_server
+
+ allow_unverified_ssl = var.unverified_ssl
+}
+
+variable "vsphere_username" {
+ type = string
+ default = ""
+}
+variable "vsphere_password" {
+ type = string
+ default = ""
+ sensitive = true
+}
+variable "vsphere_server" {
+ type = string
+ default = ""
+}
+variable "datacenter_name" {
+ type = string
+ default = ""
+}
+variable "cluster_name" {
+ type = string
+ default = ""
+}
+variable "datastore_name" {
+ type = string
+ default = ""
+ sensitive = true
+}
+variable "network_name" {
+ type = string
+ default = ""
+}
+variable "vm_template" {
+ type = string
+ default = ""
+}
+variable "unverified_ssl" {
+ type = bool
+ default = true
+}
+
+locals {
+ vm_name = "coder-${lower(data.coder_workspace_owner.me.name)}-${lower(data.coder_workspace.me.name)}"
+ root_disk_label = substr("${local.vm_name}-root", 0, 32)
+ home_volume_label = substr("${local.vm_name}-home", 0, 32)
+}
+
+data "coder_parameter" "instance_vcpus" {
+ name = "instance_vcpus"
+ display_name = "vCPUs"
+ description = "Number of vCPUs "
+ type = "number"
+ default = 1
+ mutable = true
+ option {
+ name = "1 vCPU"
+ value = 1
+ }
+ option {
+ name = "2 vCPUs"
+ value = 2
+ }
+ option {
+ name = "4 vCPUs"
+ value = 4
+ }
+ option {
+ name = "8 vCPUs"
+ value = 8
+ }
+}
+
+data "coder_parameter" "instance_memory" {
+ name = "instance_memory"
+ display_name = "Memory (GB)"
+ description = "Amount of RAM"
+ type = "number"
+ default = 2048
+ mutable = true
+ option {
+ name = "1 GB"
+ value = 1024
+ }
+ option {
+ name = "2 GB"
+ value = 2048
+ }
+ option {
+ name = "4 GB"
+ value = 4096
+ }
+ option {
+ name = "8 GB"
+ value = 8192
+ }
+ option {
+ name = "16 GB"
+ value = 16384
+ }
+ option {
+ name = "32 GB"
+ value = 32768
+ }
+}
+
+data "coder_parameter" "home_volume_size" {
+ name = "home_volume_size"
+ display_name = "Home Volume Size (GB)"
+ description = "How large would you like your home volume to be (in GB)?"
+ type = "number"
+ default = 20
+ mutable = true
+
+ validation {
+ min = 10
+ max = 1024
+ monotonic = "increasing"
+ }
+}
+
+data "coder_workspace" "me" {}
+data "coder_workspace_owner" "me" {}
+
+resource "coder_agent" "main" {
+ os = "linux"
+ arch = "amd64"
+
+ metadata {
+ key = "cpu"
+ display_name = "CPU Usage"
+ interval = 5
+ timeout = 5
+ script = "coder stat cpu"
+ }
+ metadata {
+ key = "memory"
+ display_name = "Memory Usage"
+ interval = 5
+ timeout = 5
+ script = "coder stat mem"
+ }
+ metadata {
+ key = "home"
+ display_name = "Home Usage"
+ interval = 600 # every 10 minutes
+ timeout = 30 # df can take a while on large filesystems
+ script = "coder stat disk --path /home/${lower(data.coder_workspace_owner.me.name)}"
+ }
+}
+
+data "vsphere_datacenter" "dc" {
+ name = var.datacenter_name
+}
+
+data "vsphere_datastore" "datastore" {
+ name = var.datastore_name
+ datacenter_id = data.vsphere_datacenter.dc.id
+}
+
+data "vsphere_compute_cluster" "cluster" {
+ name = var.cluster_name
+ datacenter_id = data.vsphere_datacenter.dc.id
+}
+
+data "vsphere_network" "network" {
+ name = var.network_name
+ datacenter_id = data.vsphere_datacenter.dc.id
+}
+
+data "vsphere_virtual_machine" "template" {
+ name = var.vm_template
+ datacenter_id = data.vsphere_datacenter.dc.id
+}
+
+locals {
+ cloud_init_config = templatefile("cloud-init/cloud-config.yaml.tftpl", {
+ hostname = local.vm_name
+ username = lower(data.coder_workspace_owner.me.name)
+ home_volume_label = local.home_volume_label
+ init_script = base64encode(coder_agent.main.init_script)
+ coder_agent_token = coder_agent.main.token
+ })
+}
+
+resource "vsphere_virtual_machine" "workspace" {
+ name = local.vm_name
+ firmware = data.vsphere_virtual_machine.template.firmware
+ resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
+ datastore_id = data.vsphere_datastore.datastore.id
+
+ num_cpus = data.coder_parameter.instance_vcpus.value
+ memory = data.coder_parameter.instance_memory.value
+ guest_id = data.vsphere_virtual_machine.template.guest_id
+
+ scsi_type = data.vsphere_virtual_machine.template.scsi_type
+
+ network_interface {
+ network_id = data.vsphere_network.network.id
+ adapter_type = data.vsphere_virtual_machine.template.network_interface_types[0]
+ }
+
+ disk {
+ label = "disk0"
+ size = data.vsphere_virtual_machine.template.disks.0.size
+ }
+ disk {
+ label = local.home_volume_label
+ size = data.coder_parameter.home_volume_size.value
+ unit_number = 1
+ }
+ extra_config = {
+ "guestinfo.userdata" = base64encode(local.cloud_init_config)
+ "guestinfo.userdata.encoding" = "base64"
+ }
+ clone {
+ template_uuid = data.vsphere_virtual_machine.template.id
+ }
+}
+
+module "code-server" {
+ count = data.coder_workspace.me.start_count
+ source = "registry.coder.com/coder/code-server/coder"
+ version = "~> 1.0"
+
+ agent_id = coder_agent.main.id
+ order = 1
+}
\ No newline at end of file
diff --git a/registry/coder-labs/.images/avatar.svg b/registry/coder-labs/.images/avatar.svg
index 8040fb06..676ccbf4 100644
--- a/registry/coder-labs/.images/avatar.svg
+++ b/registry/coder-labs/.images/avatar.svg
@@ -1,5 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/registry/coder/.images/avatar.svg b/registry/coder/.images/avatar.svg
index 60d7eff6..5db19df6 100644
--- a/registry/coder/.images/avatar.svg
+++ b/registry/coder/.images/avatar.svg
@@ -1,4 +1 @@
-
+
\ No newline at end of file
diff --git a/registry/coder/.images/aws-devcontainer-architecture.svg b/registry/coder/.images/aws-devcontainer-architecture.svg
index be66c3f1..c9673a37 100644
--- a/registry/coder/.images/aws-devcontainer-architecture.svg
+++ b/registry/coder/.images/aws-devcontainer-architecture.svg
@@ -1,8 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/registry/coder/.images/gcp-devcontainer-architecture.svg b/registry/coder/.images/gcp-devcontainer-architecture.svg
index c3dfd645..a53d48a7 100644
--- a/registry/coder/.images/gcp-devcontainer-architecture.svg
+++ b/registry/coder/.images/gcp-devcontainer-architecture.svg
@@ -1,8 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/registry/coder/modules/claude-code/README.md b/registry/coder/modules/claude-code/README.md
index 9a9c062f..c0004504 100644
--- a/registry/coder/modules/claude-code/README.md
+++ b/registry/coder/modules/claude-code/README.md
@@ -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.9"
+ version = "4.3.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
@@ -44,16 +44,12 @@ This example shows how to configure the Claude Code module to run the agent behi
```tf
module "claude-code" {
- source = "dev.registry.coder.com/coder/claude-code/coder"
- version = "4.2.9"
- agent_id = coder_agent.main.id
- workdir = "/home/coder/project"
- enable_boundary = true
- boundary_version = "main"
- boundary_log_dir = "/tmp/boundary_logs"
- boundary_log_level = "WARN"
- boundary_additional_allowed_urls = ["GET *google.com"]
- boundary_proxy_port = "8087"
+ source = "dev.registry.coder.com/coder/claude-code/coder"
+ version = "4.3.0"
+ agent_id = coder_agent.main.id
+ workdir = "/home/coder/project"
+ enable_boundary = true
+ boundary_version = "v0.5.1"
}
```
@@ -72,7 +68,7 @@ data "coder_parameter" "ai_prompt" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
- version = "4.2.9"
+ version = "4.3.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
@@ -108,7 +104,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.9"
+ version = "4.3.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
install_claude_code = true
@@ -130,7 +126,7 @@ variable "claude_code_oauth_token" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
- version = "4.2.9"
+ version = "4.3.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token
@@ -203,7 +199,7 @@ resource "coder_env" "bedrock_api_key" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
- version = "4.2.9"
+ version = "4.3.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
@@ -260,7 +256,7 @@ resource "coder_env" "google_application_credentials" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
- version = "4.2.9"
+ version = "4.3.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514"
diff --git a/registry/coder/modules/claude-code/main.tf b/registry/coder/modules/claude-code/main.tf
index bd160ded..62f24c36 100644
--- a/registry/coder/modules/claude-code/main.tf
+++ b/registry/coder/modules/claude-code/main.tf
@@ -210,42 +210,6 @@ variable "boundary_version" {
default = "main"
}
-variable "boundary_log_dir" {
- type = string
- description = "Directory for boundary logs"
- default = "/tmp/boundary_logs"
-}
-
-variable "boundary_log_level" {
- type = string
- description = "Log level for boundary process"
- default = "WARN"
-}
-
-variable "boundary_additional_allowed_urls" {
- type = list(string)
- description = "Additional URLs to allow through boundary (in addition to default allowed URLs)"
- default = []
-}
-
-variable "boundary_proxy_port" {
- type = string
- description = "Port for HTTP Proxy used by Boundary"
- default = "8087"
-}
-
-variable "enable_boundary_pprof" {
- type = bool
- description = "Whether to enable coder boundary pprof server"
- default = false
-}
-
-variable "boundary_pprof_port" {
- type = string
- description = "Port for pprof server used by Boundary"
- default = "6067"
-}
-
variable "compile_boundary_from_source" {
type = bool
description = "Whether to compile boundary from source instead of using the official install script"
@@ -374,12 +338,6 @@ module "agentapi" {
ARG_REPORT_TASKS='${var.report_tasks}' \
ARG_ENABLE_BOUNDARY='${var.enable_boundary}' \
ARG_BOUNDARY_VERSION='${var.boundary_version}' \
- ARG_BOUNDARY_LOG_DIR='${var.boundary_log_dir}' \
- ARG_BOUNDARY_LOG_LEVEL='${var.boundary_log_level}' \
- ARG_BOUNDARY_ADDITIONAL_ALLOWED_URLS='${join("|", var.boundary_additional_allowed_urls)}' \
- ARG_BOUNDARY_PROXY_PORT='${var.boundary_proxy_port}' \
- ARG_ENABLE_BOUNDARY_PPROF='${var.enable_boundary_pprof}' \
- ARG_BOUNDARY_PPROF_PORT='${var.boundary_pprof_port}' \
ARG_COMPILE_FROM_SOURCE='${var.compile_boundary_from_source}' \
ARG_CODER_HOST='${local.coder_host}' \
/tmp/start.sh
diff --git a/registry/coder/modules/claude-code/main.tftest.hcl b/registry/coder/modules/claude-code/main.tftest.hcl
index adfca6d2..dd9e66a6 100644
--- a/registry/coder/modules/claude-code/main.tftest.hcl
+++ b/registry/coder/modules/claude-code/main.tftest.hcl
@@ -192,10 +192,9 @@ run "test_claude_code_with_boundary" {
command = plan
variables {
- agent_id = "test-agent-boundary"
- workdir = "/home/coder/boundary-test"
- enable_boundary = true
- boundary_log_dir = "/tmp/test-boundary-logs"
+ agent_id = "test-agent-boundary"
+ workdir = "/home/coder/boundary-test"
+ enable_boundary = true
}
assert {
@@ -203,11 +202,6 @@ run "test_claude_code_with_boundary" {
error_message = "Boundary should be enabled"
}
- assert {
- condition = var.boundary_log_dir == "/tmp/test-boundary-logs"
- error_message = "Boundary log dir should be set correctly"
- }
-
assert {
condition = local.coder_host != ""
error_message = "Coder host should be extracted from access URL"
diff --git a/registry/coder/modules/claude-code/scripts/start.sh b/registry/coder/modules/claude-code/scripts/start.sh
index 061dce67..e14b26a6 100644
--- a/registry/coder/modules/claude-code/scripts/start.sh
+++ b/registry/coder/modules/claude-code/scripts/start.sh
@@ -16,11 +16,6 @@ ARG_AI_PROMPT=$(echo -n "${ARG_AI_PROMPT:-}" | base64 -d)
ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true}
ARG_ENABLE_BOUNDARY=${ARG_ENABLE_BOUNDARY:-false}
ARG_BOUNDARY_VERSION=${ARG_BOUNDARY_VERSION:-"main"}
-ARG_BOUNDARY_LOG_DIR=${ARG_BOUNDARY_LOG_DIR:-"/tmp/boundary_logs"}
-ARG_BOUNDARY_LOG_LEVEL=${ARG_BOUNDARY_LOG_LEVEL:-"WARN"}
-ARG_BOUNDARY_PROXY_PORT=${ARG_BOUNDARY_PROXY_PORT:-"8087"}
-ARG_ENABLE_BOUNDARY_PPROF=${ARG_ENABLE_BOUNDARY_PPROF:-false}
-ARG_BOUNDARY_PPROF_PORT=${ARG_BOUNDARY_PPROF_PORT:-"6067"}
ARG_COMPILE_FROM_SOURCE=${ARG_COMPILE_FROM_SOURCE:-false}
ARG_CODER_HOST=${ARG_CODER_HOST:-}
@@ -36,9 +31,6 @@ printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR"
printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS"
printf "ARG_ENABLE_BOUNDARY: %s\n" "$ARG_ENABLE_BOUNDARY"
printf "ARG_BOUNDARY_VERSION: %s\n" "$ARG_BOUNDARY_VERSION"
-printf "ARG_BOUNDARY_LOG_DIR: %s\n" "$ARG_BOUNDARY_LOG_DIR"
-printf "ARG_BOUNDARY_LOG_LEVEL: %s\n" "$ARG_BOUNDARY_LOG_LEVEL"
-printf "ARG_BOUNDARY_PROXY_PORT: %s\n" "$ARG_BOUNDARY_PROXY_PORT"
printf "ARG_COMPILE_FROM_SOURCE: %s\n" "$ARG_COMPILE_FROM_SOURCE"
printf "ARG_CODER_HOST: %s\n" "$ARG_CODER_HOST"
@@ -229,35 +221,11 @@ function start_agentapi() {
if [ "${ARG_ENABLE_BOUNDARY:-false}" = "true" ]; then
install_boundary
- mkdir -p "$ARG_BOUNDARY_LOG_DIR"
printf "Starting with coder boundary enabled\n"
- # Build boundary args with conditional --unprivileged flag
- BOUNDARY_ARGS=(--log-dir "$ARG_BOUNDARY_LOG_DIR")
# Add default allowed URLs
BOUNDARY_ARGS+=(--allow "domain=anthropic.com" --allow "domain=registry.npmjs.org" --allow "domain=sentry.io" --allow "domain=claude.ai" --allow "domain=$ARG_CODER_HOST")
- # Add any additional allowed URLs from the variable
- if [ -n "$ARG_BOUNDARY_ADDITIONAL_ALLOWED_URLS" ]; then
- IFS='|' read -ra ADDITIONAL_URLS <<< "$ARG_BOUNDARY_ADDITIONAL_ALLOWED_URLS"
- for url in "${ADDITIONAL_URLS[@]}"; do
- # Quote the URL to preserve spaces within the allow rule
- BOUNDARY_ARGS+=(--allow "$url")
- done
- fi
-
- # Set HTTP Proxy port used by Boundary
- BOUNDARY_ARGS+=(--proxy-port "$ARG_BOUNDARY_PROXY_PORT")
-
- # Set log level for boundary
- BOUNDARY_ARGS+=(--log-level "$ARG_BOUNDARY_LOG_LEVEL")
-
- if [ "${ARG_ENABLE_BOUNDARY_PPROF:-false}" = "true" ]; then
- # Enable boundary pprof server on specified port
- BOUNDARY_ARGS+=(--pprof)
- BOUNDARY_ARGS+=(--pprof-port "$ARG_BOUNDARY_PPROF_PORT")
- fi
-
agentapi server --type claude --term-width 67 --term-height 1190 -- \
boundary-run "${BOUNDARY_ARGS[@]}" -- \
claude "${ARGS[@]}"