mirror of
https://github.com/coder/registry.git
synced 2026-06-03 04:58:15 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b84d916e1 | |||
| dd412fbf34 | |||
| faff2be207 | |||
| 6acded53f6 | |||
| f3c24af1db | |||
| 143656017e | |||
| 88a0ac840f |
@@ -11,7 +11,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run check.sh
|
||||
run: |
|
||||
|
||||
@@ -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,11 +93,11 @@ 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:
|
||||
go-version: "1.23.2"
|
||||
go-version: "1.24.0"
|
||||
- name: Validate contributors
|
||||
run: go build ./cmd/readmevalidation && ./readmevalidation
|
||||
- name: Remove build file artifact
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg width='240' height='300' viewBox='0 0 240 300' fill='none' xmlns='http://www.w3.org/2000/svg'><g clip-path='url(#clip0_1401_86283)'><mask id='mask0_1401_86283' style='mask-type:luminance' maskUnits='userSpaceOnUse' x='0' y='0' width='240' height='300'><path d='M240 0H0V300H240V0Z' fill='white'/></mask><g mask='url(#mask0_1401_86283)'><path d='M180 240H60V120H180V240Z' fill='#4B4646'/><path d='M180 60H60V240H180V60ZM240 300H0V0H240V300Z' fill='#F1ECEC'/></g></g><defs><clipPath id='clip0_1401_86283'><rect width='240' height='300' fill='white'/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 577 B |
@@ -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 |
@@ -1,6 +1,6 @@
|
||||
module coder.com/coder-registry
|
||||
|
||||
go 1.23.2
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
cdr.dev/slog v1.6.1
|
||||
@@ -20,7 +20,7 @@ require (
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
go.opentelemetry.io/otel v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.16.0 // indirect
|
||||
golang.org/x/crypto v0.35.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/term v0.29.0 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/term v0.37.0 // indirect
|
||||
)
|
||||
|
||||
@@ -51,17 +51,17 @@ go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiM
|
||||
go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
|
||||
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
|
||||
go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e h1:xIXmWJ303kJCuogpj0bHq+dcjcZHU+XFyc1I0Yl9cRg=
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
display_name: OpenCode
|
||||
icon: ../../../../.icons/opencode.svg
|
||||
description: Run OpenCode AI coding assistant for AI-powered terminal assistance
|
||||
verified: false
|
||||
tags: [agent, opencode, ai, tasks]
|
||||
---
|
||||
|
||||
# OpenCode
|
||||
|
||||
Run [OpenCode](https://opencode.ai) AI coding assistant in your workspace for intelligent code generation, analysis, and development assistance. This module integrates with [AgentAPI](https://github.com/coder/agentapi) for seamless task reporting in the Coder UI.
|
||||
|
||||
```tf
|
||||
module "opencode" {
|
||||
source = "registry.coder.com/coder-labs/opencode/coder"
|
||||
version = "0.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/project"
|
||||
}
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Authentication credentials** - OpenCode auth.json file is required for non-interactive authentication, you can find this file on your system: `$HOME/.local/share/opencode/auth.json`
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage with Tasks
|
||||
|
||||
```tf
|
||||
resource "coder_ai_task" "task" {
|
||||
app_id = module.opencode.task_app_id
|
||||
}
|
||||
|
||||
module "opencode" {
|
||||
source = "registry.coder.com/coder-labs/opencode/coder"
|
||||
version = "0.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder/project"
|
||||
|
||||
ai_prompt = coder_ai_task.task.prompt
|
||||
|
||||
auth_json = <<-EOT
|
||||
{
|
||||
"google": {
|
||||
"type": "api",
|
||||
"key": "gem-xxx-xxxx"
|
||||
},
|
||||
"anthropic": {
|
||||
"type": "api",
|
||||
"key": "sk-ant-api03-xxx-xxxxxxx"
|
||||
}
|
||||
}
|
||||
EOT
|
||||
|
||||
config_json = jsonencode({
|
||||
"$schema" = "https://opencode.ai/config.json"
|
||||
mcp = {
|
||||
filesystem = {
|
||||
command = ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/home/coder/projects"]
|
||||
enabled = true
|
||||
type = "local"
|
||||
environment = {
|
||||
SOME_VARIABLE_X = "value"
|
||||
}
|
||||
}
|
||||
playwright = {
|
||||
command = ["npx", "-y", "@playwright/mcp@latest", "--headless", "--isolated"]
|
||||
enabled = true
|
||||
type = "local"
|
||||
}
|
||||
}
|
||||
model = "anthropic/claude-sonnet-4-20250514"
|
||||
})
|
||||
|
||||
pre_install_script = <<-EOT
|
||||
#!/bin/bash
|
||||
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
||||
sudo apt-get install -y nodejs
|
||||
EOT
|
||||
}
|
||||
```
|
||||
|
||||
### Standalone CLI Mode
|
||||
|
||||
Run OpenCode as a command-line tool without web interface or task reporting:
|
||||
|
||||
```tf
|
||||
module "opencode" {
|
||||
source = "registry.coder.com/coder-labs/opencode/coder"
|
||||
version = "0.1.0"
|
||||
agent_id = coder_agent.example.id
|
||||
workdir = "/home/coder"
|
||||
report_tasks = false
|
||||
cli_app = true
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter any issues, check the log files in the `~/.opencode-module` directory within your workspace for detailed information.
|
||||
|
||||
## References
|
||||
|
||||
- [Opencode JSON Config](https://opencode.ai/docs/config/)
|
||||
- [OpenCode Documentation](https://opencode.ai/docs)
|
||||
- [AgentAPI Documentation](https://github.com/coder/agentapi)
|
||||
- [Coder AI Agents Guide](https://coder.com/docs/tutorials/ai-agents)
|
||||
@@ -0,0 +1,362 @@
|
||||
import {
|
||||
test,
|
||||
afterEach,
|
||||
describe,
|
||||
setDefaultTimeout,
|
||||
beforeAll,
|
||||
expect,
|
||||
} from "bun:test";
|
||||
import { execContainer, readFileContainer, runTerraformInit } from "~test";
|
||||
import {
|
||||
loadTestFile,
|
||||
writeExecutable,
|
||||
setup as setupUtil,
|
||||
execModuleScript,
|
||||
expectAgentAPIStarted,
|
||||
} from "../../../coder/modules/agentapi/test-util";
|
||||
import dedent from "dedent";
|
||||
|
||||
let cleanupFunctions: (() => Promise<void>)[] = [];
|
||||
const registerCleanup = (cleanup: () => Promise<void>) => {
|
||||
cleanupFunctions.push(cleanup);
|
||||
};
|
||||
afterEach(async () => {
|
||||
const cleanupFnsCopy = cleanupFunctions.slice().reverse();
|
||||
cleanupFunctions = [];
|
||||
for (const cleanup of cleanupFnsCopy) {
|
||||
try {
|
||||
await cleanup();
|
||||
} catch (error) {
|
||||
console.error("Error during cleanup:", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
interface SetupProps {
|
||||
skipAgentAPIMock?: boolean;
|
||||
skipOpencodeMock?: boolean;
|
||||
moduleVariables?: Record<string, string>;
|
||||
agentapiMockScript?: string;
|
||||
}
|
||||
|
||||
const setup = async (props?: SetupProps): Promise<{ id: string }> => {
|
||||
const projectDir = "/home/coder/project";
|
||||
const { id } = await setupUtil({
|
||||
moduleDir: import.meta.dir,
|
||||
moduleVariables: {
|
||||
install_opencode: props?.skipOpencodeMock ? "true" : "false",
|
||||
install_agentapi: props?.skipAgentAPIMock ? "true" : "false",
|
||||
workdir: projectDir,
|
||||
...props?.moduleVariables,
|
||||
},
|
||||
registerCleanup,
|
||||
projectDir,
|
||||
skipAgentAPIMock: props?.skipAgentAPIMock,
|
||||
agentapiMockScript: props?.agentapiMockScript,
|
||||
});
|
||||
if (!props?.skipOpencodeMock) {
|
||||
await writeExecutable({
|
||||
containerId: id,
|
||||
filePath: "/usr/bin/opencode",
|
||||
content: await loadTestFile(import.meta.dir, "opencode-mock.sh"),
|
||||
});
|
||||
}
|
||||
return { id };
|
||||
};
|
||||
|
||||
setDefaultTimeout(60 * 1000);
|
||||
|
||||
describe("opencode", async () => {
|
||||
beforeAll(async () => {
|
||||
await runTerraformInit(import.meta.dir);
|
||||
});
|
||||
|
||||
test("happy-path", async () => {
|
||||
const { id } = await setup();
|
||||
await execModuleScript(id);
|
||||
await expectAgentAPIStarted(id);
|
||||
});
|
||||
|
||||
test("install-opencode-version", async () => {
|
||||
const version_to_install = "0.1.0";
|
||||
const { id } = await setup({
|
||||
skipOpencodeMock: true,
|
||||
moduleVariables: {
|
||||
install_opencode: "true",
|
||||
opencode_version: version_to_install,
|
||||
pre_install_script: dedent`
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Mock the opencode install for testing
|
||||
mkdir -p /home/coder/.opencode/bin
|
||||
echo '#!/bin/bash\necho "opencode mock version ${version_to_install}"' > /home/coder/.opencode/bin/opencode
|
||||
chmod +x /home/coder/.opencode/bin/opencode
|
||||
`,
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
const resp = await execContainer(id, [
|
||||
"bash",
|
||||
"-c",
|
||||
`cat /home/coder/.opencode-module/install.log`,
|
||||
]);
|
||||
expect(resp.stdout).toContain(version_to_install);
|
||||
});
|
||||
|
||||
test("check-latest-opencode-version-works", async () => {
|
||||
const { id } = await setup({
|
||||
skipOpencodeMock: true,
|
||||
skipAgentAPIMock: true,
|
||||
moduleVariables: {
|
||||
install_opencode: "true",
|
||||
pre_install_script: dedent`
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Mock the opencode install for testing
|
||||
mkdir -p /home/coder/.opencode/bin
|
||||
echo '#!/bin/bash\necho "opencode mock latest version"' > /home/coder/.opencode/bin/opencode
|
||||
chmod +x /home/coder/.opencode/bin/opencode
|
||||
`,
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
await expectAgentAPIStarted(id);
|
||||
});
|
||||
|
||||
test("opencode-auth-json", async () => {
|
||||
const authJson = JSON.stringify({
|
||||
token: "test-auth-token-123",
|
||||
user: "test-user",
|
||||
});
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
auth_json: authJson,
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
|
||||
const authFile = await readFileContainer(
|
||||
id,
|
||||
"/home/coder/.local/share/opencode/auth.json",
|
||||
);
|
||||
|
||||
expect(authFile).toContain("test-auth-token-123");
|
||||
expect(authFile).toContain("test-user");
|
||||
});
|
||||
|
||||
test("opencode-config-json", async () => {
|
||||
const configJson = JSON.stringify({
|
||||
$schema: "https://opencode.ai/config.json",
|
||||
mcp: {
|
||||
test: {
|
||||
command: ["test-cmd"],
|
||||
type: "local",
|
||||
},
|
||||
},
|
||||
model: "anthropic/claude-sonnet-4-20250514",
|
||||
});
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
config_json: configJson,
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
|
||||
const configFile = await readFileContainer(
|
||||
id,
|
||||
"/home/coder/.config/opencode/opencode.json",
|
||||
);
|
||||
expect(configFile).toContain("test-cmd");
|
||||
expect(configFile).toContain("anthropic/claude-sonnet-4-20250514");
|
||||
});
|
||||
|
||||
test("opencode-ai-prompt", async () => {
|
||||
const prompt = "This is a task prompt for OpenCode.";
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
ai_prompt: prompt,
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
|
||||
const resp = await execContainer(id, [
|
||||
"bash",
|
||||
"-c",
|
||||
`cat /home/coder/.opencode-module/agentapi-start.log`,
|
||||
]);
|
||||
expect(resp.stdout).toContain(prompt);
|
||||
});
|
||||
|
||||
test("opencode-continue-flag", async () => {
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
continue: "true",
|
||||
ai_prompt: "test prompt",
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
|
||||
const startLog = await execContainer(id, [
|
||||
"bash",
|
||||
"-c",
|
||||
"cat /home/coder/.opencode-module/agentapi-start.log",
|
||||
]);
|
||||
expect(startLog.stdout).toContain("--continue");
|
||||
});
|
||||
|
||||
test("opencode-continue-with-session-id", async () => {
|
||||
const sessionId = "session-123";
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
continue: "true",
|
||||
session_id: sessionId,
|
||||
ai_prompt: "test prompt",
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
|
||||
const startLog = await execContainer(id, [
|
||||
"bash",
|
||||
"-c",
|
||||
"cat /home/coder/.opencode-module/agentapi-start.log",
|
||||
]);
|
||||
expect(startLog.stdout).toContain("--continue");
|
||||
expect(startLog.stdout).toContain(`--session ${sessionId}`);
|
||||
});
|
||||
|
||||
test("opencode-session-id", async () => {
|
||||
const sessionId = "session-123";
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
session_id: sessionId,
|
||||
ai_prompt: "test prompt",
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
|
||||
const startLog = await execContainer(id, [
|
||||
"bash",
|
||||
"-c",
|
||||
"cat /home/coder/.opencode-module/agentapi-start.log",
|
||||
]);
|
||||
expect(startLog.stdout).toContain(`--session ${sessionId}`);
|
||||
});
|
||||
|
||||
test("opencode-report-tasks-enabled", async () => {
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
report_tasks: "true",
|
||||
ai_prompt: "test prompt",
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
|
||||
const startLog = await execContainer(id, [
|
||||
"bash",
|
||||
"-c",
|
||||
"cat /home/coder/.opencode-module/agentapi-start.log",
|
||||
]);
|
||||
expect(startLog.stdout).toContain(
|
||||
"report your progress using coder_report_task",
|
||||
);
|
||||
});
|
||||
|
||||
test("opencode-report-tasks-disabled", async () => {
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
report_tasks: "false",
|
||||
ai_prompt: "test prompt",
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
|
||||
const startLog = await execContainer(id, [
|
||||
"bash",
|
||||
"-c",
|
||||
"cat /home/coder/.opencode-module/agentapi-start.log",
|
||||
]);
|
||||
expect(startLog.stdout).not.toContain(
|
||||
"report your progress using coder_report_task",
|
||||
);
|
||||
});
|
||||
|
||||
test("cli-app-creation", async () => {
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
cli_app: "true",
|
||||
cli_app_display_name: "OpenCode Terminal",
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
// CLI app creation is handled by the agentapi module
|
||||
// We just verify the setup completed successfully
|
||||
await expectAgentAPIStarted(id);
|
||||
});
|
||||
|
||||
test("pre-post-install-scripts", async () => {
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
pre_install_script: "#!/bin/bash\necho 'opencode-pre-install-script'",
|
||||
post_install_script: "#!/bin/bash\necho 'opencode-post-install-script'",
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
|
||||
const preInstallLog = await readFileContainer(
|
||||
id,
|
||||
"/home/coder/.opencode-module/pre_install.log",
|
||||
);
|
||||
expect(preInstallLog).toContain("opencode-pre-install-script");
|
||||
|
||||
const postInstallLog = await readFileContainer(
|
||||
id,
|
||||
"/home/coder/.opencode-module/post_install.log",
|
||||
);
|
||||
expect(postInstallLog).toContain("opencode-post-install-script");
|
||||
});
|
||||
|
||||
test("workdir-variable", async () => {
|
||||
const workdir = "/home/coder/opencode-test-folder";
|
||||
const { id } = await setup({
|
||||
skipOpencodeMock: false,
|
||||
moduleVariables: {
|
||||
workdir,
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
|
||||
const resp = await readFileContainer(
|
||||
id,
|
||||
"/home/coder/.opencode-module/agentapi-start.log",
|
||||
);
|
||||
expect(resp).toContain(workdir);
|
||||
});
|
||||
|
||||
test("subdomain-enabled", async () => {
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
subdomain: "true",
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
// Subdomain configuration is handled by the agentapi module
|
||||
// We just verify the setup completed successfully
|
||||
await expectAgentAPIStarted(id);
|
||||
});
|
||||
|
||||
test("custom-display-names", async () => {
|
||||
const { id } = await setup({
|
||||
moduleVariables: {
|
||||
web_app_display_name: "Custom OpenCode Web",
|
||||
cli_app_display_name: "Custom OpenCode CLI",
|
||||
cli_app: "true",
|
||||
},
|
||||
});
|
||||
await execModuleScript(id);
|
||||
// Display names are handled by the agentapi module
|
||||
// We just verify the setup completed successfully
|
||||
await expectAgentAPIStarted(id);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,203 @@
|
||||
terraform {
|
||||
required_version = ">= 1.0"
|
||||
|
||||
required_providers {
|
||||
coder = {
|
||||
source = "coder/coder"
|
||||
version = ">= 2.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "agent_id" {
|
||||
type = string
|
||||
description = "The ID of a Coder agent."
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
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 "icon" {
|
||||
type = string
|
||||
description = "The icon to use for the app."
|
||||
default = "/icon/opencode.svg"
|
||||
}
|
||||
|
||||
variable "workdir" {
|
||||
type = string
|
||||
description = "The folder to run OpenCode in."
|
||||
}
|
||||
|
||||
variable "report_tasks" {
|
||||
type = bool
|
||||
description = "Whether to enable task reporting to Coder UI via AgentAPI"
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "cli_app" {
|
||||
type = bool
|
||||
description = "Whether to create a CLI app for OpenCode"
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "web_app_display_name" {
|
||||
type = string
|
||||
description = "Display name for the web app"
|
||||
default = "OpenCode"
|
||||
}
|
||||
|
||||
variable "cli_app_display_name" {
|
||||
type = string
|
||||
description = "Display name for the CLI app"
|
||||
default = "OpenCode CLI"
|
||||
}
|
||||
|
||||
variable "pre_install_script" {
|
||||
type = string
|
||||
description = "Custom script to run before installing OpenCode."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "post_install_script" {
|
||||
type = string
|
||||
description = "Custom script to run after installing OpenCode."
|
||||
default = null
|
||||
}
|
||||
|
||||
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.11.2"
|
||||
}
|
||||
|
||||
variable "ai_prompt" {
|
||||
type = string
|
||||
description = "Initial task prompt for OpenCode."
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "subdomain" {
|
||||
type = bool
|
||||
description = "Whether to use a subdomain for AgentAPI."
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "install_opencode" {
|
||||
type = bool
|
||||
description = "Whether to install OpenCode."
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "opencode_version" {
|
||||
type = string
|
||||
description = "The version of OpenCode to install."
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "continue" {
|
||||
type = bool
|
||||
description = "continue the last session. Uses the --continue flag"
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "session_id" {
|
||||
type = string
|
||||
description = "Session id to continue. Passed via --session"
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "auth_json" {
|
||||
type = string
|
||||
description = "Your auth.json from $HOME/.local/share/opencode/auth.json, Required for non-interactive authentication"
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "config_json" {
|
||||
type = string
|
||||
description = "OpenCode JSON config. https://opencode.ai/docs/config/"
|
||||
default = ""
|
||||
}
|
||||
|
||||
locals {
|
||||
workdir = trimsuffix(var.workdir, "/")
|
||||
app_slug = "opencode"
|
||||
install_script = file("${path.module}/scripts/install.sh")
|
||||
start_script = file("${path.module}/scripts/start.sh")
|
||||
module_dir_name = ".opencode-module"
|
||||
}
|
||||
|
||||
module "agentapi" {
|
||||
source = "registry.coder.com/coder/agentapi/coder"
|
||||
version = "2.0.0"
|
||||
|
||||
agent_id = var.agent_id
|
||||
web_app_slug = local.app_slug
|
||||
web_app_order = var.order
|
||||
web_app_group = var.group
|
||||
web_app_icon = var.icon
|
||||
web_app_display_name = var.web_app_display_name
|
||||
cli_app = var.cli_app
|
||||
cli_app_slug = var.cli_app ? "${local.app_slug}-cli" : null
|
||||
cli_app_display_name = var.cli_app ? var.cli_app_display_name : null
|
||||
agentapi_subdomain = var.subdomain
|
||||
folder = local.workdir
|
||||
module_dir_name = local.module_dir_name
|
||||
install_agentapi = var.install_agentapi
|
||||
agentapi_version = var.agentapi_version
|
||||
pre_install_script = var.pre_install_script
|
||||
post_install_script = var.post_install_script
|
||||
start_script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh
|
||||
chmod +x /tmp/start.sh
|
||||
|
||||
ARG_WORKDIR='${local.workdir}' \
|
||||
ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \
|
||||
ARG_SESSION_ID='${var.session_id}' \
|
||||
ARG_REPORT_TASKS='${var.report_tasks}' \
|
||||
ARG_CONTINUE='${var.continue}' \
|
||||
/tmp/start.sh
|
||||
EOT
|
||||
|
||||
install_script = <<-EOT
|
||||
#!/bin/bash
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
|
||||
echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh
|
||||
chmod +x /tmp/install.sh
|
||||
ARG_OPENCODE_VERSION='${var.opencode_version}' \
|
||||
ARG_MCP_APP_STATUS_SLUG='${local.app_slug}' \
|
||||
ARG_INSTALL_OPENCODE='${var.install_opencode}' \
|
||||
ARG_REPORT_TASKS='${var.report_tasks}' \
|
||||
ARG_WORKDIR='${local.workdir}' \
|
||||
ARG_AUTH_JSON='${var.auth_json != null ? base64encode(replace(var.auth_json, "'", "'\\''")) : ""}' \
|
||||
ARG_OPENCODE_CONFIG='${var.config_json != null ? base64encode(replace(var.config_json, "'", "'\\''")) : ""}' \
|
||||
/tmp/install.sh
|
||||
EOT
|
||||
}
|
||||
|
||||
output "task_app_id" {
|
||||
value = module.agentapi.task_app_id
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
run "defaults_are_correct" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.install_opencode == true
|
||||
error_message = "OpenCode installation should be enabled by default"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.install_agentapi == true
|
||||
error_message = "AgentAPI installation should be enabled by default"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.agentapi_version == "v0.11.2"
|
||||
error_message = "Default AgentAPI version should be 'v0.11.2'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.opencode_version == "latest"
|
||||
error_message = "Default OpenCode version should be 'latest'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.report_tasks == true
|
||||
error_message = "Task reporting should be enabled by default"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.cli_app == false
|
||||
error_message = "CLI app should be disabled by default"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.subdomain == false
|
||||
error_message = "Subdomain should be disabled by default"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.web_app_display_name == "OpenCode"
|
||||
error_message = "Default web app display name should be 'OpenCode'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.cli_app_display_name == "OpenCode CLI"
|
||||
error_message = "Default CLI app display name should be 'OpenCode CLI'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = local.app_slug == "opencode"
|
||||
error_message = "App slug should be 'opencode'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = local.module_dir_name == ".opencode-module"
|
||||
error_message = "Module dir name should be '.opencode-module'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = local.workdir == "/home/coder/project"
|
||||
error_message = "Workdir should be trimmed of trailing slash"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.continue == false
|
||||
error_message = "Continue flag should be disabled by default"
|
||||
}
|
||||
}
|
||||
|
||||
run "workdir_trailing_slash_trimmed" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project/"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = local.workdir == "/home/coder/project"
|
||||
error_message = "Workdir should be trimmed of trailing slash"
|
||||
}
|
||||
}
|
||||
|
||||
run "opencode_version_configuration" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
opencode_version = "v1.0.0"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.opencode_version == "v1.0.0"
|
||||
error_message = "OpenCode version should be set correctly"
|
||||
}
|
||||
}
|
||||
|
||||
run "agentapi_version_configuration" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
agentapi_version = "v0.9.0"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.agentapi_version == "v0.9.0"
|
||||
error_message = "AgentAPI version should be set correctly"
|
||||
}
|
||||
}
|
||||
|
||||
run "cli_app_configuration" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
cli_app = true
|
||||
cli_app_display_name = "Custom OpenCode CLI"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.cli_app == true
|
||||
error_message = "CLI app should be enabled when specified"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.cli_app_display_name == "Custom OpenCode CLI"
|
||||
error_message = "Custom CLI app display name should be set"
|
||||
}
|
||||
}
|
||||
|
||||
run "web_app_configuration" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
web_app_display_name = "Custom OpenCode Web"
|
||||
order = 5
|
||||
group = "AI Tools"
|
||||
icon = "/custom/icon.svg"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.web_app_display_name == "Custom OpenCode Web"
|
||||
error_message = "Custom web app display name should be set"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.order == 5
|
||||
error_message = "Custom order should be set"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.group == "AI Tools"
|
||||
error_message = "Custom group should be set"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.icon == "/custom/icon.svg"
|
||||
error_message = "Custom icon should be set"
|
||||
}
|
||||
}
|
||||
|
||||
run "ai_configuration_variables" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
ai_prompt = "This is a test prompt"
|
||||
session_id = "session-123"
|
||||
continue = true
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.ai_prompt == "This is a test prompt"
|
||||
error_message = "AI prompt should be set correctly"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.session_id == "session-123"
|
||||
error_message = "Session ID should be set correctly"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.continue == true
|
||||
error_message = "Continue flag should be set correctly"
|
||||
}
|
||||
}
|
||||
|
||||
run "auth_json_configuration" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
auth_json = "{\"token\": \"test-token\", \"user\": \"test-user\"}"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.auth_json != ""
|
||||
error_message = "Auth JSON should be set"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = can(jsondecode(var.auth_json))
|
||||
error_message = "Auth JSON should be valid JSON"
|
||||
}
|
||||
}
|
||||
|
||||
run "config_json_configuration" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
config_json = "{\"$schema\": \"https://opencode.ai/config.json\", \"mcp\": {\"test\": {\"command\": [\"test-cmd\"], \"type\": \"local\"}}, \"model\": \"anthropic/claude-sonnet-4-20250514\"}"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.config_json != ""
|
||||
error_message = "OpenCode JSON configuration should be set"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = can(jsondecode(var.config_json))
|
||||
error_message = "OpenCode JSON configuration should be valid JSON"
|
||||
}
|
||||
}
|
||||
|
||||
run "task_reporting_configuration" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
report_tasks = false
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.report_tasks == false
|
||||
error_message = "Task reporting should be disabled when specified"
|
||||
}
|
||||
}
|
||||
|
||||
run "subdomain_configuration" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
subdomain = true
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.subdomain == true
|
||||
error_message = "Subdomain should be enabled when specified"
|
||||
}
|
||||
}
|
||||
|
||||
run "install_flags_configuration" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
install_opencode = false
|
||||
install_agentapi = false
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.install_opencode == false
|
||||
error_message = "OpenCode installation should be disabled when specified"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.install_agentapi == false
|
||||
error_message = "AgentAPI installation should be disabled when specified"
|
||||
}
|
||||
}
|
||||
|
||||
run "custom_scripts_configuration" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
pre_install_script = "#!/bin/bash\necho 'pre-install'"
|
||||
post_install_script = "#!/bin/bash\necho 'post-install'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.pre_install_script != null
|
||||
error_message = "Pre-install script should be set"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.post_install_script != null
|
||||
error_message = "Post-install script should be set"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = can(regex("pre-install", var.pre_install_script))
|
||||
error_message = "Pre-install script should contain expected content"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = can(regex("post-install", var.post_install_script))
|
||||
error_message = "Post-install script should contain expected content"
|
||||
}
|
||||
}
|
||||
|
||||
run "empty_variables_handled_correctly" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
ai_prompt = ""
|
||||
session_id = ""
|
||||
auth_json = ""
|
||||
config_json = ""
|
||||
continue = false
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.ai_prompt == ""
|
||||
error_message = "Empty AI prompt should be handled correctly"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.session_id == ""
|
||||
error_message = "Empty session ID should be handled correctly"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.auth_json == ""
|
||||
error_message = "Empty auth JSON should be handled correctly"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.config_json == ""
|
||||
error_message = "Empty config JSON should be handled correctly"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.continue == false
|
||||
error_message = "Continue flag default should be handled correctly"
|
||||
}
|
||||
}
|
||||
|
||||
run "continue_flag_configuration" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent"
|
||||
workdir = "/home/coder/project"
|
||||
continue = true
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.continue == true
|
||||
error_message = "Continue flag should be enabled when specified"
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
command_exists() {
|
||||
command -v "$1" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"}
|
||||
ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true}
|
||||
ARG_MCP_APP_STATUS_SLUG=${ARG_MCP_APP_STATUS_SLUG:-}
|
||||
ARG_OPENCODE_VERSION=${ARG_OPENCODE_VERSION:-latest}
|
||||
ARG_INSTALL_OPENCODE=${ARG_INSTALL_OPENCODE:-true}
|
||||
ARG_AUTH_JSON=$(echo -n "$ARG_AUTH_JSON" | base64 -d 2> /dev/null || echo "")
|
||||
ARG_OPENCODE_CONFIG=$(echo -n "$ARG_OPENCODE_CONFIG" | base64 -d 2> /dev/null || echo "")
|
||||
|
||||
# Print all received environment variables
|
||||
printf "=== INSTALL CONFIG ===\n"
|
||||
printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR"
|
||||
printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS"
|
||||
printf "ARG_MCP_APP_STATUS_SLUG: %s\n" "$ARG_MCP_APP_STATUS_SLUG"
|
||||
printf "ARG_OPENCODE_VERSION: %s\n" "$ARG_OPENCODE_VERSION"
|
||||
printf "ARG_INSTALL_OPENCODE: %s\n" "$ARG_INSTALL_OPENCODE"
|
||||
if [ -n "$ARG_AUTH_JSON" ]; then
|
||||
printf "ARG_AUTH_JSON: [AUTH DATA RECEIVED]\n"
|
||||
else
|
||||
printf "ARG_AUTH_JSON: [NOT PROVIDED]\n"
|
||||
fi
|
||||
if [ -n "$ARG_OPENCODE_CONFIG" ]; then
|
||||
printf "ARG_OPENCODE_CONFIG: [RECEIVED]\n"
|
||||
else
|
||||
printf "ARG_OPENCODE_CONFIG: [NOT PROVIDED]\n"
|
||||
fi
|
||||
printf "==================================\n"
|
||||
|
||||
install_opencode() {
|
||||
if [ "$ARG_INSTALL_OPENCODE" = "true" ]; then
|
||||
if ! command_exists opencode; then
|
||||
echo "Installing OpenCode (version: ${ARG_OPENCODE_VERSION})..."
|
||||
if [ "$ARG_OPENCODE_VERSION" = "latest" ]; then
|
||||
curl -fsSL https://opencode.ai/install | bash
|
||||
else
|
||||
VERSION=$ARG_OPENCODE_VERSION curl -fsSL https://opencode.ai/install | bash
|
||||
fi
|
||||
export PATH=/home/coder/.opencode/bin:$PATH
|
||||
printf "Opencode location: %s\n" "$(which opencode)"
|
||||
if ! command_exists opencode; then
|
||||
echo "ERROR: Failed to install OpenCode"
|
||||
exit 1
|
||||
fi
|
||||
echo "OpenCode installed successfully"
|
||||
else
|
||||
echo "OpenCode already installed"
|
||||
fi
|
||||
else
|
||||
echo "OpenCode installation skipped (ARG_INSTALL_OPENCODE=false)"
|
||||
fi
|
||||
}
|
||||
|
||||
setup_opencode_config() {
|
||||
local opencode_config_file="$HOME/.config/opencode/opencode.json"
|
||||
local auth_json_file="$HOME/.local/share/opencode/auth.json"
|
||||
|
||||
mkdir -p "$(dirname "$auth_json_file")"
|
||||
mkdir -p "$(dirname "$opencode_config_file")"
|
||||
|
||||
setup_opencode_auth "$auth_json_file"
|
||||
|
||||
if [ -n "$ARG_OPENCODE_CONFIG" ]; then
|
||||
echo "Writing to the config file"
|
||||
echo "$ARG_OPENCODE_CONFIG" > "$opencode_config_file"
|
||||
fi
|
||||
|
||||
if [ "$ARG_REPORT_TASKS" = "true" ]; then
|
||||
setup_coder_mcp_server "$opencode_config_file"
|
||||
fi
|
||||
|
||||
echo "MCP configuration completed: $opencode_config_file"
|
||||
}
|
||||
|
||||
setup_opencode_auth() {
|
||||
local auth_json_file="$1"
|
||||
|
||||
if [ -n "$ARG_AUTH_JSON" ]; then
|
||||
echo "$ARG_AUTH_JSON" > "$auth_json_file"
|
||||
printf "added auth json to %s" "$auth_json_file"
|
||||
else
|
||||
printf "auth json not provided"
|
||||
fi
|
||||
}
|
||||
|
||||
setup_coder_mcp_server() {
|
||||
local opencode_config_file="$1"
|
||||
|
||||
# Set environment variables based on task reporting setting
|
||||
echo "Configuring OpenCode task reporting"
|
||||
export CODER_MCP_APP_STATUS_SLUG="$ARG_MCP_APP_STATUS_SLUG"
|
||||
export CODER_MCP_AI_AGENTAPI_URL="http://localhost:3284"
|
||||
echo "Coder integration configured for task reporting"
|
||||
|
||||
# Add coder MCP server configuration to the JSON file
|
||||
echo "Adding Coder MCP server configuration"
|
||||
|
||||
# Create the coder server configuration JSON
|
||||
coder_config=$(
|
||||
cat << EOF
|
||||
{
|
||||
"type": "local",
|
||||
"command": ["coder", "exp", "mcp", "server"],
|
||||
"enabled": true,
|
||||
"environment": {
|
||||
"CODER_MCP_APP_STATUS_SLUG": "${CODER_MCP_APP_STATUS_SLUG:-}",
|
||||
"CODER_MCP_AI_AGENTAPI_URL": "${CODER_MCP_AI_AGENTAPI_URL:-}",
|
||||
"CODER_AGENT_URL": "${CODER_AGENT_URL:-}",
|
||||
"CODER_AGENT_TOKEN": "${CODER_AGENT_TOKEN:-}",
|
||||
"CODER_MCP_ALLOWED_TOOLS": "coder_report_task"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
temp_file=$(mktemp)
|
||||
jq --argjson coder_config "$coder_config" '.mcp.coder = $coder_config' "$opencode_config_file" > "$temp_file"
|
||||
mv "$temp_file" "$opencode_config_file"
|
||||
echo "Coder MCP server configuration added"
|
||||
|
||||
}
|
||||
|
||||
install_opencode
|
||||
setup_opencode_config
|
||||
|
||||
echo "OpenCode module setup completed."
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
export PATH=/home/coder/.opencode/bin:$PATH
|
||||
|
||||
command_exists() {
|
||||
command -v "$1" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"}
|
||||
ARG_AI_PROMPT=$(echo -n "${ARG_AI_PROMPT:-}" | base64 -d 2> /dev/null || echo "")
|
||||
ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true}
|
||||
ARG_SESSION_ID=${ARG_SESSION_ID:-}
|
||||
ARG_CONTINUE=${ARG_CONTINUE:-false}
|
||||
|
||||
# Print all received environment variables
|
||||
printf "=== START CONFIG ===\n"
|
||||
printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR"
|
||||
printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS"
|
||||
printf "ARG_CONTINUE: %s\n" "$ARG_CONTINUE"
|
||||
printf "ARG_SESSION_ID: %s\n" "$ARG_SESSION_ID"
|
||||
if [ -n "$ARG_AI_PROMPT" ]; then
|
||||
printf "ARG_AI_PROMPT: [AI PROMPT RECEIVED]\n"
|
||||
else
|
||||
printf "ARG_AI_PROMPT: [NOT PROVIDED]\n"
|
||||
fi
|
||||
printf "==================================\n"
|
||||
|
||||
OPENCODE_ARGS=()
|
||||
AGENTAPI_ARGS=()
|
||||
|
||||
validate_opencode_installation() {
|
||||
if ! command_exists opencode; then
|
||||
printf "ERROR: OpenCode not installed. Set install_opencode to true\n"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
build_opencode_args() {
|
||||
|
||||
if [ -n "$ARG_SESSION_ID" ]; then
|
||||
OPENCODE_ARGS+=(--session "$ARG_SESSION_ID")
|
||||
fi
|
||||
|
||||
if [ "$ARG_CONTINUE" = "true" ]; then
|
||||
OPENCODE_ARGS+=(--continue)
|
||||
fi
|
||||
|
||||
if [ -n "$ARG_AI_PROMPT" ]; then
|
||||
if [ "$ARG_REPORT_TASKS" = "true" ]; then
|
||||
PROMPT="Every step of the way, report your progress using coder_report_task tool with proper summary and statuses. Your task at hand: $ARG_AI_PROMPT"
|
||||
else
|
||||
PROMPT="$ARG_AI_PROMPT"
|
||||
fi
|
||||
AGENTAPI_ARGS+=(-I "$PROMPT")
|
||||
fi
|
||||
}
|
||||
|
||||
start_agentapi() {
|
||||
printf "Starting in directory: %s\n" "$ARG_WORKDIR"
|
||||
cd "$ARG_WORKDIR"
|
||||
|
||||
build_opencode_args
|
||||
|
||||
printf "Running OpenCode with args: %s\n" "${OPENCODE_ARGS[*]}"
|
||||
echo agentapi server "${AGENTAPI_ARGS[@]}" --type opencode --term-width 67 --term-height 1190 -- opencode "${OPENCODE_ARGS[@]}"
|
||||
agentapi server "${AGENTAPI_ARGS[@]}" --type opencode --term-width 67 --term-height 1190 -- opencode "${OPENCODE_ARGS[@]}"
|
||||
}
|
||||
|
||||
validate_opencode_installation
|
||||
start_agentapi
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Mock OpenCode CLI for testing purposes
|
||||
# This script simulates the OpenCode command-line interface
|
||||
|
||||
echo "OpenCode Mock CLI - Test Version"
|
||||
echo "Args received: $*"
|
||||
|
||||
# Simulate opencode behavior based on arguments
|
||||
case "$1" in
|
||||
--version | -v)
|
||||
echo "opencode mock version 0.1.0-test"
|
||||
;;
|
||||
--help | -h)
|
||||
echo "OpenCode Mock Help"
|
||||
echo "Usage: opencode [options] [command]"
|
||||
echo "This is a mock version for testing"
|
||||
;;
|
||||
*)
|
||||
echo "Running OpenCode mock with arguments: $*"
|
||||
echo "Mock execution completed successfully"
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@@ -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
|
||||
|
||||
@@ -14,7 +14,7 @@ Automatically install and run mux in a Coder workspace. By default, the module i
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
@@ -35,7 +35,7 @@ module "mux" {
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
@@ -46,7 +46,7 @@ module "mux" {
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
# Default is "latest"; set to a specific version to pin
|
||||
install_version = "0.4.0"
|
||||
@@ -59,7 +59,7 @@ module "mux" {
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
port = 8080
|
||||
}
|
||||
@@ -73,7 +73,7 @@ Run an existing copy of mux if found, otherwise install from npm:
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
use_cached = true
|
||||
}
|
||||
@@ -87,7 +87,7 @@ Run without installing from the network (requires mux to be pre-installed):
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
agent_id = coder_agent.example.id
|
||||
install = false
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ describe("mux", async () => {
|
||||
expect(output.exitCode).toBe(0);
|
||||
const expectedLines = [
|
||||
"📦 Installing mux via npm into /tmp/mux...",
|
||||
"⏭️ Skipping npm lifecycle scripts with --ignore-scripts",
|
||||
"🥳 mux has been installed in /tmp/mux",
|
||||
"🚀 Starting mux server on port 4000...",
|
||||
"Check logs at /tmp/mux.log!",
|
||||
@@ -62,5 +63,5 @@ describe("mux", async () => {
|
||||
for (const line of expectedLines) {
|
||||
expect(output.stdout).toContain(line);
|
||||
}
|
||||
}, 60000);
|
||||
}, 180000);
|
||||
});
|
||||
|
||||
@@ -50,13 +50,14 @@ if [ ! -f "$MUX_BINARY" ] || [ "${USE_CACHED}" != true ]; then
|
||||
if [ ! -f package.json ]; then
|
||||
echo '{}' > package.json
|
||||
fi
|
||||
echo "⏭️ Skipping npm lifecycle scripts with --ignore-scripts"
|
||||
PKG="mux"
|
||||
if [ -z "${VERSION}" ] || [ "${VERSION}" = "latest" ]; then
|
||||
PKG_SPEC="$PKG@latest"
|
||||
else
|
||||
PKG_SPEC="$PKG@${VERSION}"
|
||||
fi
|
||||
if ! npm install --no-audit --no-fund --omit=dev "$PKG_SPEC"; then
|
||||
if ! npm install --no-audit --no-fund --omit=dev --ignore-scripts "$PKG_SPEC"; then
|
||||
echo "❌ Failed to install mux via npm"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -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."
|
||||
}
|
||||
Reference in New Issue
Block a user