Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .icons/devin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 69 additions & 0 deletions registry/coder/modules/devin-desktop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
---
display_name: Devin Desktop
description: Add a one-click button to launch Devin Desktop (formerly Windsurf Editor)
icon: ../../../../.icons/devin.svg
verified: true
tags: [ide, devin, ai]
---

# Devin Desktop

Add a button to open any workspace with a single click in Devin Desktop.

Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder).

```tf
module "devin-desktop" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/devin-desktop/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
}
```

## Examples

### Open in a specific directory

```tf
module "devin-desktop" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/devin-desktop/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
}
```

### Configure MCP servers for Devin Desktop

Provide a JSON-encoded string via the `mcp` input. When set, the module writes the value to `~/.config/devin/mcp_config.json` using a `coder_script` on workspace start.

The following example configures Devin Desktop to use the GitHub MCP server with authentication facilitated by the [`coder_external_auth`](https://coder.com/docs/admin/external-auth#configure-a-github-oauth-app) resource.

```tf
module "devin-desktop" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/devin-desktop/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
mcp = jsonencode({
mcpServers = {
"github" : {
"url" : "https://api.githubcopilot.com/mcp/",
"headers" : {
"Authorization" : "Bearer ${data.coder_external_auth.github.access_token}",
},
"type" : "http"
}


}
})
}

data "coder_external_auth" "github" {
id = "github"
}
```
132 changes: 132 additions & 0 deletions registry/coder/modules/devin-desktop/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { describe, expect, it } from "bun:test";
import {
runTerraformApply,
runTerraformInit,
testRequiredVariables,
runContainer,
execContainer,
removeContainer,
findResourceInstance,
readFileContainer,
} from "~test";

describe("devin-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.devin_desktop_url.value).toBe(
"devin://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.module === "module.vscode-desktop-core" &&
res.name === "vscode-desktop",
);

expect(coder_app).not.toBeNull();
expect(coder_app?.instances.length).toBe(1);
expect(coder_app?.instances[0].attributes.order).toBeNull();
expect(coder_app?.instances[0].attributes.slug).toBe("devin-desktop");
expect(coder_app?.instances[0].attributes.display_name).toBe(
"Devin Desktop",
);
});

it("adds folder", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
});
expect(state.outputs.devin_desktop_url.value).toBe(
"devin://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.devin_desktop_url.value).toBe(
"devin://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",
open_recent: false,
});
expect(state.outputs.devin_desktop_url.value).toBe(
"devin://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.devin_desktop_url.value).toBe(
"devin://coder.coder-remote/open?owner=default&workspace=default&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});

it("allows overriding slug and display_name", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
slug: "devin",
display_name: "Devin",
});
const coder_app = state.resources.find(
(res) =>
res.type === "coder_app" &&
res.module === "module.vscode-desktop-core" &&
res.name === "vscode-desktop",
);
expect(coder_app?.instances[0].attributes.slug).toBe("devin");
expect(coder_app?.instances[0].attributes.display_name).toBe("Devin");
});

it("writes ~/.config/devin/mcp_config.json when mcp provided", async () => {
const id = await runContainer("alpine");
try {
const mcp = JSON.stringify({
servers: { demo: { url: "http://localhost:1234" } },
});
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
mcp,
});
const script = findResourceInstance(
state,
"coder_script",
"devin_desktop_mcp",
).script;
const resp = await execContainer(id, ["sh", "-c", script]);
if (resp.exitCode !== 0) {
console.log(resp.stdout);
console.log(resp.stderr);
}
expect(resp.exitCode).toBe(0);
const content = await readFileContainer(
id,
"/root/.config/devin/mcp_config.json",
);
expect(content).toBe(mcp);
} finally {
await removeContainer(id);
}
});
});
108 changes: 108 additions & 0 deletions registry/coder/modules/devin-desktop/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
terraform {
required_version = ">= 1.0"

required_providers {
coder = {
source = "coder/coder"
version = ">= 2.5"
}
}
}

variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}

variable "folder" {
type = string
description = "The folder to open in Devin Desktop."
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
}

variable "slug" {
type = string
description = "The slug of the app."
default = "devin-desktop"
}

variable "display_name" {
type = string
description = "The display name of the app."
default = "Devin Desktop"
}

variable "mcp" {
type = string
description = "JSON-encoded string to configure MCP servers for Devin Desktop. When set, writes ~/.config/devin/mcp_config.json."
default = ""
}

data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}

locals {
mcp_b64 = var.mcp != "" ? base64encode(var.mcp) : ""
}

# Devin Desktop is Cognition's rebrand of the Windsurf Editor (June 2, 2026),
# which was itself Codeium's rebrand of its original editor. It uses the same
# VS Code Desktop launch mechanism as the windsurf module, so this wraps the
# same shared vscode-desktop-core module with Devin Desktop's branding.
module "vscode-desktop-core" {
source = "registry.coder.com/coder/vscode-desktop-core/coder"
version = "1.0.2"

agent_id = var.agent_id

coder_app_icon = "/icon/devin.svg"
coder_app_slug = var.slug
coder_app_display_name = var.display_name
coder_app_order = var.order
coder_app_group = var.group

folder = var.folder
open_recent = var.open_recent
# devin:// is registered as an external app protocol in coder/coder
# (ALLOWED_EXTERNAL_APP_PROTOCOLS, coder/coder#28214).
protocol = "devin"
}

resource "coder_script" "devin_desktop_mcp" {
count = var.mcp != "" ? 1 : 0
agent_id = var.agent_id
display_name = "Devin Desktop MCP"
icon = "/icon/devin.svg"
run_on_start = true
start_blocks_login = false
script = <<-EOT
#!/bin/sh
set -eu
mkdir -p "$HOME/.config/devin"
echo -n "${local.mcp_b64}" | base64 -d > "$HOME/.config/devin/mcp_config.json"
chmod 600 "$HOME/.config/devin/mcp_config.json"
EOT
}

output "devin_desktop_url" {
value = module.vscode-desktop-core.ide_uri
description = "Devin Desktop URL."
}
11 changes: 8 additions & 3 deletions registry/coder/modules/windsurf/README.md
Comment thread
matifali marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ tags: [ide, windsurf, ai]

# Windsurf Editor

> [!IMPORTANT]
> Windsurf Editor was rebranded to Devin Desktop by Cognition on June 2, 2026. New templates should use the
> [`devin-desktop`](https://registry.coder.com/modules/coder/devin-desktop) module instead. This module is kept
> published and fully functional for existing templates that reference it; it is not being removed.

Add a button to open any workspace with a single click in Windsurf Editor.

Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder).
Expand All @@ -16,7 +21,7 @@ Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder)
module "windsurf" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/windsurf/coder"
version = "1.3.1"
version = "1.3.2"
agent_id = coder_agent.main.id
}
```
Expand All @@ -29,7 +34,7 @@ module "windsurf" {
module "windsurf" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/windsurf/coder"
version = "1.3.1"
version = "1.3.2"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
}
Expand All @@ -45,7 +50,7 @@ The following example configures Windsurf to use the GitHub MCP server with auth
module "windsurf" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/windsurf/coder"
version = "1.3.1"
version = "1.3.2"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
mcp = jsonencode({
Expand Down
Loading