Compare commits

..

104 Commits

Author SHA1 Message Date
Atif Ali 4b7128b17e chore: link vscode-web and code-server comparison docs (#607)
link vscode-web and code-server comparison docs in README. Also fixes a
drive-by version to match expected versioning.

Co-authored-by: DevCats <christofer@coder.com>
2025-12-17 15:46:10 -06:00
DevCats 77a3e74e0b feat: enhance version bump script and CI workflow with ci mode (#615)
## Description

Add CI mode to version bump script to output Pass or Fail with a less
verbose comment when it fails
<!-- Briefly describe what this PR does and why -->

## Type of Change

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

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2025-12-17 15:31:11 -06:00
Danielle Maywood 311de23454 refactor(coder-labs/auggie): support terraform provider coder v2.12.0 (#494)
## Description

Updates the module to use the new version of the agentapi module for
Coder 2.28

## Type of Change

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

## Module Information

**Path:** `registry/coder-labs/modules/auggie`  
**New version:** `v0.3.0`  
**Breaking change:** [x] Yes [ ] No

## Testing & Validation

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

## Related Issues

- https://github.com/coder/internal/issues/1065

## Related PRs

- https://github.com/coder/registry/pull/485

Co-authored-by: DevCats <christofer@coder.com>
2025-12-17 13:16:12 -06:00
Danielle Maywood f66f61d724 refactor(coder-labs/sourcegraph-amp): support terraform provider coder v2.12.0 (#495)
## Description

Updates the module to use the new version of the agentapi module for
Coder 2.28

## Type of Change

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

## Module Information

**Path:** `registry/coder-labs/modules/sourcegraph-amp`  
**New version:** `v3.0.0`  
**Breaking change:** [x] Yes [ ] No

## Testing & Validation

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

## Related Issues

- https://github.com/coder/internal/issues/1065

## Related PRs

- https://github.com/coder/registry/pull/485

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-12-17 11:54:28 -06:00
Danielle Maywood 631bf027c6 refactor(coder-labs/gemini): support terraform provider coder v2.12.0 (#493)
## Description

Updates the module to use the new version of the agentapi module for
Coder 2.28

## Type of Change

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

## Module Information

**Path:** `registry/[namespace]/modules/[module-name]`  
**New version:** `v3.0.0`  
**Breaking change:** [x] Yes [ ] No

## Testing & Validation

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

## Related Issues

- https://github.com/coder/internal/issues/1065

## Related PRs

- https://github.com/coder/registry/pull/485

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-12-17 10:00:44 -06:00
Danielle Maywood eb4c28fc61 refactor(coder-labs/copilot): support terraform provider coder v2.12.0 (#492)
## Description

Updates the module to use the new version of the agentapi module for
Coder 2.28

## Type of Change

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

## Module Information

**Path:** `registry/coder-labs/modules/copilot`  
**New version:** `v0.3.0`  
**Breaking change:** [x] Yes [ ] No

## Testing & Validation

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

## Related Issues

- https://github.com/coder/internal/issues/1065

## Related PRs

- https://github.com/coder/registry/pull/485

Co-authored-by: DevCats <christofer@coder.com>
2025-12-17 15:19:21 +00:00
Danielle Maywood c551c4d84a refactor(coder-labs/cursor-cli): support terraform provider coder v2.12.0 (#491)
## Description

Updates the module to use the new version of the agentapi module for
Coder 2.28

## Type of Change

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

## Module Information

**Path:** `registry/coder-labs/modules/cursor-cli`  
**New version:** `v0.3.0`  
**Breaking change:** [x] Yes [ ] No

## Testing & Validation

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

## Related Issues

- https://github.com/coder/internal/issues/1065

## Related PRs

- https://github.com/coder/registry/pull/485

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-12-17 15:12:34 +00:00
imgbot[bot] 4b9da4036a [ImgBot] Optimize images (#608)
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2025-12-16 23:25:21 +05:00
Excellencedev 96c5f3219d Add Hetzner Cloud server template example (#560)
## Description

This PR adds a template example for the Hetzner Cloud Sever. 


This video shows the provisioning of multiple hetzner instances in coder
with dynamic param enabled, running simultaneously and shown in the
Hetzner console, checking labels on both the OS Filesystem and on the
hetzner console and then shutting down through coder demonstrating clean
up on hetzner and deleting workspaces without errors
DEMO VIDEO:
https://drive.google.com/file/d/1JkhjszCRM9K27XDlMi_2nXtJosoflns_/view?usp=sharing

## Type of Change

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


## Template Information

**Path:** `registry/Excellencedev/templates/hetzner-linux`

## Testing & Validation

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

## Related Issues
/closes #209 
/claim #209

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: DevCats <christofer@coder.com>
2025-12-16 14:03:52 +00:00
Marcin Tojek 146540c1e9 feat: add perplexica module (#596) 2025-12-16 13:00:06 +00:00
35C4n0r a85436fdf4 chore(claude-code): use $HOME variable instead of hardcoded path and remove symlink (#592)
## Description

- Remove hardcoded `/home/coder` path.
- Remove symlink in favour of coder_env "PATH".

## Type of Change

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

## Module Information

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

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
Co-authored-by: DevelopmentCats <christofer@coder.com>
2025-12-15 15:07:14 -06:00
netsgnut aa4890fe62 fix(kasmvnc): update kasmvnc desktop environment list to match upstream (#598)
## Description

This PR makes the following changes to `coder/modules/kasmvnc`:

- Update desktop environment list for the KasmVNC module

Currently upstream supports a number of [additional Desktop
Environments](https://github.com/kasmtech/KasmVNC/blob/v1.4.0/unix/vncserver.man#L56-L67).
The change updates the list so that DEs like Mate are supported. Do note
that `manual` is also supported if `$HOME/.vnc/xstartup` is supplied, so
this has been added as another option, too.

## Type of Change

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

## Module Information

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

**Path:** `registry/coder/modules/kasmvnc`
**New version:** `v1.2.7`  
**Breaking change:** [ ] Yes [X] No

## Testing & Validation

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

## Related Issues

None

Co-authored-by: DevCats <christofer@coder.com>
2025-12-15 13:00:18 -06:00
blinkagent[bot] ab6799ac07 fix(git-clone): use unique temp file for post_clone_script to avoid race condition (#601)
## Summary

Fixes a race condition when multiple `git-clone` modules with
`post_clone_script` run concurrently.

## Problem

All instances of the git-clone module use the same hardcoded
`/tmp/post_clone.sh` path. When multiple modules run concurrently (or
overlap), they collide on the same temp file, causing:

```
rm: cannot remove '/tmp/post_clone.sh': No such file or directory
```

This results in a non-zero exit code, causing the workspace to appear
unhealthy.

## Solution

Use `mktemp` to generate a unique temporary filename for each module
instance:

```bash
POST_CLONE_TMP=$(mktemp /tmp/post_clone_XXXXXX.sh)
```

This ensures each concurrent execution uses its own temp file,
eliminating the race condition.

Fixes #600

---------

Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
Co-authored-by: Matyas Danter <mdanter@gmail.com>
2025-12-15 11:19:11 -06:00
Atif Ali bda3eb96e8 fix(coder/modules/zed): update main example to version 1.1.3 (#603) 2025-12-13 17:08:50 +05:00
Atif Ali 6b16cd3529 fix(coder/modules/zed): change script shebang from sh to bash (#602) 2025-12-13 16:46:02 +05:30
35C4n0r 43d05a9da4 feat(coder/agentapi/test-utils): add feature to optionally use coder_env (#595)
## Description
`setup` now returns `coderEnvVariables` that can be used in
`execModuleScript`.

## Type of Change

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

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Signed-off-by: 35C4n0r <work.jaykumar@gmail.com>
2025-12-12 19:03:48 +05:30
DevCats e3f8b6450e chore: fix source references and cleanup readme (#593)
## Description

Update source references in README to
`registry.coder.com/harleylrn/kiro-cli/coder` from
`registry.coder.com/coder/kiro-cli/coder`

Remove verbose tf docs from README since it is visualized in the
registry.

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

## Type of Change

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

## Module Information

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

**Path:** `registry/harleylrn/modules/kiro-cli`  
**New version:** `v1.0.1`  
**Breaking change:** [ ] Yes [X] No

## Testing & Validation

- [X] Tests pass (`bun test`)
- [X] Code formatted (`bun fmt`)
- [X] Changes tested locally
2025-12-09 14:46:56 -06:00
Michael Orlov c03986f9cb feat: add kiro-cli module (#552)
## Description

Due to rebranding of the Amazon Q to kiro-cli, new module was created

## Type of Change

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

## Module Information

All defined in the README
**Path:** `registry/harleylrn/modules/kiro-cli`  
**New version:** `v1.0.0`  
**Breaking change:** [ ] Yes [x] No

## Testing & Validation

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

## Related Issues

Resolves #547

---------

Co-authored-by: DevCats <christofer@coder.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Harley Davidson <harleylrn@users.noreply.github.com>
2025-12-09 11:52:05 -06:00
Michael Suchacz 758aba4c2a fix(mux): delete server.lock before starting server (#591)
## Summary

Remove stale `~/.mux/server.lock` file before starting the mux server to
prevent startup failures when a previous server didn't clean up
properly.

## Changes

- Added `rm -f "${HOME}/.mux/server.lock"` at the start of the `run_mux`
function
- Bumped version to 1.0.5

## Testing

- All Terraform tests pass
- Shellcheck passes with only style warnings (unrelated to this change)

---------

Co-authored-by: Atif Ali <atif@coder.com>
2025-12-09 21:33:01 +05:00
35C4n0r d745117782 chore(coder/modules/claude-code): bump agentapi version in claude-code (#590)
## Description
bump agentapi version to 0.11.4

## Type of Change

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

## Module Information

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

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


## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-12-09 21:35:50 +05:30
DevCats a99d3385c3 refactor(claude-code): simplify session resumption logic for standalone and task mode (#579)
## Description

Brings session resumption up to speed with current claude-code
capabilities, and should make session resumption less prone to errors
across the board.

I am still testing this further to ensure that all logic path's are
verified.

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

## Type of Change

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

## Module Information

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

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

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: 35C4n0r <70096901+35C4n0r@users.noreply.github.com>
Co-authored-by: Atif Ali <atif@coder.com>
2025-12-09 09:28:58 -06:00
35C4n0r c62fe569a0 fix(registry/coder/claude): fix versions in claude-code readme (#589)
## Description

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

## Type of Change

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

## Module Information

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

**Path:** `registry/[namespace]/modules/[module-name]`  
**New version:** `v1.0.0`  
**Breaking change:** [ ] Yes [ ] No

## Template Information

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

**Path:** `registry/[namespace]/templates/[template-name]`

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2025-12-09 14:49:48 +05:30
Atif Ali ce2087bc09 chore: update development dependencies versions (#588) 2025-12-09 08:39:13 +00:00
35C4n0r 67f18cd4de chore(registry/coder/claude-code): hides coder_report_task tool messages (#586)
## Description
- Bump agentapi version for feature: hide coder_report_task tool
messages

## Type of Change

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

## Module Information

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

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

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2025-12-09 13:58:35 +05:30
Atif Ali e0697562c1 fix(positron): fix readme version and verification status (#587) 2025-12-09 13:35:29 +05:30
Marcin Tojek 499aaa676c feat: add open-webui module (#580)
This module installs and runs Open WebUI using Python and pip within
your Coder workspace.
2025-12-08 13:20:10 -06:00
Rowan Smith 3ae8c7dcff feat: support optional installation of vault enterprise binary (#582)
## Description

When using the SAML auth method with Vault and authenticating via CLI it
is required to use the enterprise version of the binary, as SAML support
is not built into the non enterprise version of the CLI. This PR adds an
optional `enterprise` variable to support this.

@matifali can you let me know the appropriate tag command to run to
release this once approved, please?

## Type of Change

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

## Module Information

**Path:** `registry/coder/modules/vault-cli`  
**New version:** `v1.1.0`  
**Breaking change:** [ ] Yes [x] No


## Testing & Validation

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

## Related Issues

None
2025-12-08 07:56:03 -06:00
Atif Ali 2cfbe5f69c feat: add vault-cli module with optional token configuration (#575) 2025-12-04 11:11:35 +05:00
35C4n0r 186e0c4de6 feat(module/coder-labs/amp): add mode flag and disable animation (#550)
## Description
1. "mode" flag: Set the agent mode (free, rush, smart) — controls the
model, system prompt, and tool selection.
2. `"amp.terminal.animation": false`: This disables the animation.
3. Update the readme

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

## Type of Change

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

## Module Information

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

**Path:** `registry/coder-labs/modules/amp`  
**New version:** `v2.1.0`  
**Breaking change:** [ ] Yes [X] No

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-12-03 20:27:14 +00:00
DevCats 69e5dc5c80 feat: new google antigravity ide module with icon (#577)
## Description

<!-- Briefly describe what this PR does and why -->
Adds a module to open coder workspaces in Antigravity.

Redoing PR with icon included

## Type of Change

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

## Module Information

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

**Path:** `registry/coder/modules/antigravity`  
**New version:** `v1.0.0`  
**Breaking change:** [ ] Yes [ ] No

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: Atif Ali <atif@coder.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-02 15:41:21 -06:00
Atif Ali b143b7d9ba Revert "feat: add Antigravity IDE module" (#576)
Reverts coder/registry#558
2025-12-02 14:02:47 -06:00
DevCats 0a8930d60d feat: add Antigravity IDE module (#558)
## Description

<!-- Briefly describe what this PR does and why -->
Adds a module to open coder workspaces in Antigravity.

## Type of Change

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

## Module Information

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

**Path:** `registry/coder/modules/antigravity`  
**New version:** `v1.0.0`  
**Breaking change:** [ ] Yes [ ] No

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: Atif Ali <atif@coder.com>
2025-12-02 13:51:49 -06:00
Atif Ali d21db0d220 fix(jfrog-oauth): fail when access_token is empty (#574)
## Summary

Fixes #72 - The `jfrog-oauth` module now fails with a clear error
message when the JFrog access token is empty, instead of silently
creating configurations with empty tokens.

## Changes

### 1. Added Precondition Validation (`main.tf`)

```hcl
lifecycle {
  precondition {
    condition     = data.coder_external_auth.jfrog.access_token != ""
    error_message = "JFrog access token is empty. Please authenticate with JFrog using external auth."
  }
}
```

This ensures the module fails at **plan time** with a clear error when
users haven't authenticated via external auth.

### 2. Replaced `main.test.ts` with `jfrog-oauth.tftest.hcl`

**Why we removed the TypeScript tests:**

The TypeScript tests used `runTerraformApply()` which runs `terraform
apply` directly. This approach **cannot mock data sources** like
`coder_external_auth`. The Coder provider returns empty strings for
tokens by default when running outside a real Coder workspace.

With our new precondition, the TypeScript tests would always fail
because:
1. `terraform apply` runs → empty `access_token` from mock provider
2. Precondition check fails → "JFrog access token is empty"
3. Test fails before any assertions run

**The solution:** Terraform's native `.tftest.hcl` format supports
`override_data` blocks that can properly mock data sources:

```hcl
override_data {
  target = data.coder_external_auth.jfrog
  values = {
    access_token = "valid-token-value"  # or "" to test failure
  }
}
```

### 3. Comprehensive Test Coverage

The new `jfrog-oauth.tftest.hcl` includes **12 tests** (up from 7):

| Test | What it validates |
|------|------------------|
| `test_required_vars` | Basic module works with required variables |
| `test_empty_access_token_fails` | **NEW:** Precondition rejects empty
tokens |
| `test_valid_access_token_succeeds` | Module works with valid token |
| `test_jfrog_url_validation` | **NEW:** URL must start with http(s)://
|
| `test_username_field_validation` | **NEW:** Must be "email" or
"username" |
| `test_with_npm_package_manager` | NPM config with scoped repos (script
content) |
| `test_configure_code_server` | **NEW:** IDE env vars created when
enabled |
| `test_go_proxy_env` | GOPROXY env value with multiple repos |
| `test_pypi_package_manager` | pip.conf with extra-index-url |
| `test_docker_package_manager` | register_docker commands for all repos
|
| `test_conda_package_manager` | .condarc channels configuration |
| `test_maven_package_manager` | settings.xml with servers and repos |

All package manager tests use `strcontains()` to verify the actual
script content matches expected configuration formats.

## Test Limitations (Acknowledged)

The tests verify **template rendering** but not **runtime execution**:

|  What we test |  What we don't test |
|----------------|----------------------|
| Configuration file formats | Script syntax errors at runtime |
| Variable interpolation | JFrog CLI compatibility |
| Precondition validation | Actual JFrog authentication |
| Script contains expected content | Commands execute successfully |

**Rationale:** The original TypeScript tests also only checked script
content (`toContain()`), not execution. Full execution testing would
require a mock JFrog server, which adds significant complexity for
limited benefit. The script is straightforward bash that configures
files and runs CLI commands.

## Testing

```bash
cd registry/coder/modules/jfrog-oauth
terraform test
# Success! 12 passed, 0 failed.
```

_Generated with [mux](https://github.com/coder/mux)_
2025-12-02 13:17:39 -06:00
Atif Ali 392f6b120a fix(mux): move image to shared folder and fix path (#573)
## Summary
Fix the mux module image rendering by moving the image to the shared
images folder and updating the path.

## Changes
- Move `product-hero.webp` from `registry/coder/modules/mux/.images/` to
`registry/coder/.images/`
- Rename to `mux-product-hero.webp` for consistency with other module
images (e.g., `amazon-q.png`, `amazon-dcv-windows.png`)
- Update README path from `.images/product-hero.webp` to
`../../.images/mux-product-hero.webp`

This follows the same pattern as other modules like `amazon-q`,
`amazon-dcv-windows`, and `jetbrains-gateway` which all use
`../../.images/` paths.
2025-12-02 12:42:05 -06:00
Atif Ali 7de72fc7cc feat(mux): add GitHub link, set verified, bump to v1.0.3 (#572) 2025-12-02 23:03:49 +05:00
dependabot[bot] 3e1ddbf624 chore(deps): bump crate-ci/typos from 1.39.2 to 1.40.0 in the github-actions group (#570)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 14:15:39 +05:00
Phorcys 0021a9fe7d feat: update vscode-based desktop IDE modules to use vscode-desktop-core (#279) 2025-11-27 17:35:40 +05:00
DevCats 70ca76e86d ci: add shellcheck step (#484) 2025-11-27 12:00:04 +05:00
Phorcys 7c4ef92c8c chore: update all module READMEs to use main agent id (#567) 2025-11-26 13:37:19 -06:00
35C4n0r 7b84d916e1 feat: add opencode module (#515)
## Description

This PR adds the opencode module to the registry.

## Type of Change

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

## Module Information

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

**Path:** `registry/coder-labs/modules/opencode`  
**New version:** `v0.1.0`  
**Breaking change:** [ ] Yes [x] No

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-11-26 10:31:58 -06:00
DevCats dd412fbf34 fix(claude-code): change mcp add command to use mcp add-json instead (#564)
## Description

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

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

## Type of Change

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

## Module Information

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

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

## Testing & Validation

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

## Related Issues
#562 
<!-- Link related issues or write "None" if not applicable -->
2025-11-25 11:44:12 -06:00
dependabot[bot] faff2be207 chore(deps): bump actions/checkout from 5 to 6 in the github-actions group (#561)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 07:47:55 +00:00
Shahar Zrihen 6acded53f6 added positron desktop ide module based on vs code desktop (#528)
Co-authored-by: DevCats <chris@dualriver.com>
Co-authored-by: Atif Ali <atif@coder.com>
2025-11-23 09:19:09 +00:00
35C4n0r f3c24af1db fix(coder/modules/claude-code): check existing session-ids when using --session-id flag (#557) 2025-11-21 14:54:01 +05:30
Michael Suchacz 143656017e fix(coder/modules/mux): skipping post install scripts in mux (#556)
## Description

Skip post install scripts when installing mux. 

## Type of Change

- Bug fix

## Module Information

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

**Path:** `registry/coder/modules/mux`  
**New version:** `v1.0.1`  
**Breaking change:** No

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

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-11-20 11:59:04 -06:00
dependabot[bot] 88a0ac840f chore(deps): bump golang.org/x/crypto from 0.35.0 to 0.45.0 in the go_modules group across 1 directory (#553)
Bumps the go_modules group with 1 update in the / directory:
[golang.org/x/crypto](https://github.com/golang/crypto).

Updates `golang.org/x/crypto` from 0.35.0 to 0.45.0
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/golang/crypto/commit/4e0068c0098be10d7025c99ab7c50ce454c1f0f9"><code>4e0068c</code></a>
go.mod: update golang.org/x dependencies</li>
<li><a
href="https://github.com/golang/crypto/commit/e79546e28b85ea53dd37afe1c4102746ef553b9c"><code>e79546e</code></a>
ssh: curb GSSAPI DoS risk by limiting number of specified OIDs</li>
<li><a
href="https://github.com/golang/crypto/commit/f91f7a7c31bf90b39c1de895ad116a2bacc88748"><code>f91f7a7</code></a>
ssh/agent: prevent panic on malformed constraint</li>
<li><a
href="https://github.com/golang/crypto/commit/2df4153a0311bdfea44376e0eb6ef2faefb0275b"><code>2df4153</code></a>
acme/autocert: let automatic renewal work with short lifetime certs</li>
<li><a
href="https://github.com/golang/crypto/commit/bcf6a849efcf4702fa5172cb0998b46c3da1e989"><code>bcf6a84</code></a>
acme: pass context to request</li>
<li><a
href="https://github.com/golang/crypto/commit/b4f2b62076abeee4e43fb59544dac565715fbf1e"><code>b4f2b62</code></a>
ssh: fix error message on unsupported cipher</li>
<li><a
href="https://github.com/golang/crypto/commit/79ec3a51fcc7fbd2691d56155d578225ccc542e2"><code>79ec3a5</code></a>
ssh: allow to bind to a hostname in remote forwarding</li>
<li><a
href="https://github.com/golang/crypto/commit/122a78f140d9d3303ed3261bc374bbbca149140f"><code>122a78f</code></a>
go.mod: update golang.org/x dependencies</li>
<li><a
href="https://github.com/golang/crypto/commit/c0531f9c34514ad5c5551e2d6ce569ca673a8afd"><code>c0531f9</code></a>
all: eliminate vet diagnostics</li>
<li><a
href="https://github.com/golang/crypto/commit/0997000b45e3a40598272081bcad03ffd21b8adb"><code>0997000</code></a>
all: fix some comments</li>
<li>Additional commits viewable in <a
href="https://github.com/golang/crypto/compare/v0.35.0...v0.45.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang.org/x/crypto&package-manager=go_modules&previous-version=0.35.0&new-version=0.45.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/coder/registry/network/alerts).

</details>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: DevCats <christofer@coder.com>
Co-authored-by: Atif Ali <atif@coder.com>
2025-11-20 22:54:59 +05:00
Rowan Smith 5f3a559e83 feat: Add support for Vault namespaces to Vault modules (#554)
## Description

Adds support for accessing auth mounts/secret engines located in a non
root namespace. Namespaces is a feature of Vault Enterprise.

## Type of Change

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

## Module Information

**Path:** `registry/coder/modules/vault-github`  
**New version:** `v1.1.0`  
**Breaking change:** [ ] Yes [x] No

**Path:** `registry/coder/modules/vault-jwt`  
**New version:** `v1.2.0`  
**Breaking change:** [ ] Yes [x] No

**Path:** `registry/coder/modules/vault-token`  
**New version:** `v1.3.0`  
**Breaking change:** [ ] Yes [x] No

## Testing & Validation

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

## Related Issues

None

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-11-20 10:48:13 -06:00
DevCats b4c162d281 fix: version-bump script fix (#546)
## Description

This pull request improves the version bump script making it more robust
for future registry changes.
<!-- Briefly describe what this PR does and why -->

## Type of Change

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

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
https://github.com/coder/registry/issues/510
2025-11-20 08:07:34 -06:00
Phorcys e58fd5d5da chore: remove verified tag on community modules (#555)
## Description

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

## Type of Change

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

## Testing & Validation

- [ ] Tests pass (`bun test`)
- [ ] Code formatted (`bun fmt`)
- [ ] Changes tested locally
2025-11-20 14:32:52 +05:00
DevCats 73a92bea6e feat: add disable_autoupdater variable to control auto-updates (#545)
## Description

Claude-Code auto-updates itself unless the `DISABLE_AUTOUPDATER` env is
set. This PR adds a `disable_autoupdater` variable which allows the user
to disable claude-code's auto updating feature. This should resolve the
issue where claude-code updates itself when a user defines a specific
claude-code version to be installed which was confusing for the end
user.

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

## Type of Change

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

## Module Information

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

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

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2025-11-18 09:42:40 -06:00
DevCats 71c84a8bb2 fix: jfrog oauth username extraction from oauth jwt token (#539)
## Description

Add username extraction from jfrog JWT OAuth token with fallback to
coder username.
<!-- Briefly describe what this PR does and why -->

## Type of Change

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

## Module Information

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

**Path:** `registry/coder/modules/jfrog-oauth`  
**New version:** `v1.2.2`  
**Breaking change:** [ ] Yes [X] No

## Template Information

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

**Path:** `registry/[namespace]/templates/[template-name]`

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2025-11-17 12:28:05 -06:00
Yevhenii Shcherbina e11ed2d7ae fix: remove allowed host (#541)
- Use official installation script for boundary instead of compiling
from source.
- Use boundary-run wrapper.
2025-11-17 10:12:59 -05:00
Atif Ali 8add161f53 fix(jfrog): replace deprecated --repo-resolve flag for Maven (#535)
Fixes deprecated Maven config flags in jfrog-oauth and jfrog-token
modules
closes #534

Done using GitHub Copilot.

---------

Co-authored-by: GitHub Actions Bot <github-actions[bot]@users.noreply.github.com>
Co-authored-by: DevelopmentCats <christofer@coder.com>
2025-11-17 15:17:09 +01:00
dependabot[bot] ddf86e7087 chore(deps): bump crate-ci/typos from 1.39.0 to 1.39.2 in the github-actions group (#544)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-17 13:20:53 +05:00
Michael Suchacz c12fca57ad fix: add back cmux icon (#542)
## Description

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

## Type of Change

- [ ] New module
- [ ] New template
- [ ] Bug fix
- [ ] Feature/enhancement
- [ ] Documentation
- [ ] Other
2025-11-16 00:39:11 +01:00
Michael Suchacz 0e3263fd6f fix: change cmux npm package to mux (#533) 2025-11-15 17:57:42 +00:00
djarbz f304201b6f Add IDE metadata output with tests and examples (#526)
## Description

Exposes the metadata of the selected IDEs for use in the main template.
Also adds tests to verify that the output metadata matches the "default"
mappings.

## Type of Change

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

## Module Information

**Path:** `registry/coder/modules/jetbrains`  
**New version:** `v1.2.0`  
**Breaking change:** [ ] Yes [x] No

## Testing & Validation

- [N/A] Tests pass (`bun test`)
- [x] Code formatted (`bun fmt`)
- [x] Changes tested locally

## Related Issues

None

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-11-14 15:47:01 -06:00
blinkagent[bot] 8bf1789996 feat: add additional_args variable to code-server module (#536)
This PR adds an `extra_args` variable to the code-server module,
allowing users to pass additional command-line arguments to code-server.

## Changes

- Added `additional_args` variable to `main.tf` with a default empty
string
- Updated `run.sh` to include `${ADDITIONAL_ARGS}` in the code-server
command
- Added documentation and example usage in `README.md`

## Use Case

This solves the issue where users want to disable the workspace trust
prompt by passing `--disable-workspace-trust` to code-server. See the
discussion in
https://codercom.slack.com/archives/C09H8LRLG8K/p1762983278455979

## Example Usage

```tf
module "code-server" {
  source               = "registry.coder.com/coder/code-server/coder"
  version              = "1.3.1"
  agent_id             = coder_agent.example.id
  additional_args      = "--disable-workspace-trust"
}
```

The `additional_args` variable can accept any additional command-line
arguments that code-server supports.

---------

Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
Co-authored-by: DevelopmentCats <christofer@coder.com>
2025-11-13 07:43:16 -06:00
35C4n0r 9e89f04691 fix: session resumption fix, and bug fixes for arg path logic (#522)
## Description

Fix issue with commands being injected through prompt.

Bug fix for logic in arg paths.
<!-- Briefly describe what this PR does and why -->

## Type of Change

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

## Module Information

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

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

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: DevelopmentCats <christofer@coder.com>
Co-authored-by: DevelopmentCats <chris@dualriver.com>
2025-11-12 09:33:37 -06:00
Danielle Maywood 4edfdae572 refactor(coder-labs/tasks-docker): support coder/claude-code 4.0.0 (#497)
Updates the template to use the new version of the claude-code module
for the Coder 2.28 release. This closely matches the [built-in
template](https://github.com/coder/coder/blob/main/examples/templates/tasks-docker/main.tf)
defined in [coder/coder](https://github.com/coder/coder).
2025-11-12 10:31:13 +00:00
Anis Khalfallah 69abf48390 test: add terraform tests to kiro module (#529)
## Description

Add terraform tests to kiro module

## Type of Change

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

## Module Information

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

**Path:** `registry/coder/modules/kiro`  
**Breaking change:** [ ] Yes [x] No

## Testing & Validation

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

## Related Issues

#308

Signed-off-by: Anis KHALFALLAH <khalfallah.anis@hotmail.com>
Co-authored-by: DevCats <christofer@coder.com>
2025-11-10 15:34:44 -06:00
dependabot[bot] 578ed89697 chore(deps): bump the github-actions group across 1 directory with 2 updates (#530)
Bumps the github-actions group with 2 updates in the / directory:
[crate-ci/typos](https://github.com/crate-ci/typos) and
[golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action).

Updates `crate-ci/typos` from 1.38.1 to 1.39.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/releases">crate-ci/typos's
releases</a>.</em></p>
<blockquote>
<h2>v1.39.0</h2>
<h2>[1.39.0] - 2025-10-31</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1383">October
2025</a> changes</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>When a typo is pluralized, prefer pluralized corrections</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/blob/master/CHANGELOG.md">crate-ci/typos's
changelog</a>.</em></p>
<blockquote>
<h2>[1.39.0] - 2025-10-31</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1383">October
2025</a> changes</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>When a typo is pluralized, prefer pluralized corrections</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/crate-ci/typos/commit/07d900b8fa1097806b8adb6391b0d3e0ac2fdea7"><code>07d900b</code></a>
chore: Release</li>
<li><a
href="https://github.com/crate-ci/typos/commit/fcce1f892d5149dd02bcdce2cabdbfd58609fdf0"><code>fcce1f8</code></a>
chore: Release</li>
<li><a
href="https://github.com/crate-ci/typos/commit/85692fd91b604adb2caa6c9852a6693c84c330e0"><code>85692fd</code></a>
docs: Update changelog</li>
<li><a
href="https://github.com/crate-ci/typos/commit/da7527cc3513111180ccc1f1635559fcb13c03c2"><code>da7527c</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1406">#1406</a>
from epage/oct</li>
<li><a
href="https://github.com/crate-ci/typos/commit/9046b5b2e97e5b58560fc4d6ca00bb1629b5272f"><code>9046b5b</code></a>
feat(dict): October additions</li>
<li><a
href="https://github.com/crate-ci/typos/commit/9a86c0a0c033d31643ca72c09323c8ea4ad8154c"><code>9a86c0a</code></a>
docs: Update screenshot</li>
<li>See full diff in <a
href="https://github.com/crate-ci/typos/compare/v1.38.1...v1.39.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `golangci/golangci-lint-action` from 8 to 9
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/golangci/golangci-lint-action/releases">golangci/golangci-lint-action's
releases</a>.</em></p>
<blockquote>
<h2>v9.0.0</h2>
<p>In the scope of this release, we change Nodejs runtime from node20 to
node24 (<a
href="https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/">https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/</a>).</p>
<h2>What's Changed</h2>
<h3>Changes</h3>
<ul>
<li>feat: add install-only option by <a
href="https://github.com/ldez"><code>@​ldez</code></a> in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1305">golangci/golangci-lint-action#1305</a></li>
<li>feat: support Module Plugin System by <a
href="https://github.com/ldez"><code>@​ldez</code></a> in <a
href="https://redirect.github.com/golangci/golangci-lint-action/pull/1306">golangci/golangci-lint-action#1306</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/golangci/golangci-lint-action/compare/v8.0.0...v9.0.0">https://github.com/golangci/golangci-lint-action/compare/v8.0.0...v9.0.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/0a35821d5c230e903fcfe077583637dea1b27b47"><code>0a35821</code></a>
docs: update readme</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/043b1b8d1c47e4591c1719682a050a7a0a82e19c"><code>043b1b8</code></a>
feat: support Module Plugin System (<a
href="https://redirect.github.com/golangci/golangci-lint-action/issues/1306">#1306</a>)</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/a66d26a4652b1a0b28a56b7c8b194c20f7c0b7f6"><code>a66d26a</code></a>
feat: add install-only option (<a
href="https://redirect.github.com/golangci/golangci-lint-action/issues/1305">#1305</a>)</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/7fe1b22e0c4632d6260fedfafd4b6025ac7418c3"><code>7fe1b22</code></a>
build(deps): bump the dependencies group with 2 updates (<a
href="https://redirect.github.com/golangci/golangci-lint-action/issues/1303">#1303</a>)</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/14973f18c82b6d66679563f71666ccee11907cb2"><code>14973f1</code></a>
build(deps-dev): bump the dev-dependencies group with 2 updates (<a
href="https://redirect.github.com/golangci/golangci-lint-action/issues/1299">#1299</a>)</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/8c2d575d9b37153325eebc4bb3a94cd09e1fae5d"><code>8c2d575</code></a>
build(deps): bump <code>@​types/node</code> from 24.8.1 to 24.9.1 in the
dependencies group...</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/b002b6ecfcabe6ac0e2c6cba1bcc779eb34ac51f"><code>b002b6e</code></a>
build(deps): bump actions/setup-node from 5 to 6 (<a
href="https://redirect.github.com/golangci/golangci-lint-action/issues/1296">#1296</a>)</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/c13f4ed1a9a677a28be0df3e11c34a78db85c77c"><code>c13f4ed</code></a>
build(deps): bump <code>@​types/node</code> from 24.7.2 to 24.8.1 in the
dependencies group...</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/b68d21b131098f33ec55c11c242113b4a10dc30a"><code>b68d21b</code></a>
docs: improve readme</li>
<li><a
href="https://github.com/golangci/golangci-lint-action/commit/06188a2a4a13a4786b4584e086b2040214cd4ca5"><code>06188a2</code></a>
build(deps): bump github/codeql-action from 3 to 4 (<a
href="https://redirect.github.com/golangci/golangci-lint-action/issues/1293">#1293</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/golangci/golangci-lint-action/compare/v8...v9">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: DevCats <christofer@coder.com>
2025-11-10 07:56:03 -06:00
Michael Suchacz f0ccb20846 fix: bump version in README.md (#532)
## Description

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

## Type of Change

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

## Module Information

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

**Path:** `registry/[namespace]/modules/[module-name]`  
**New version:** `v1.0.0`  
**Breaking change:** [ ] Yes [ ] No

## Template Information

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

**Path:** `registry/[namespace]/templates/[template-name]`

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2025-11-10 14:41:36 +01:00
Michael Suchacz e357fcf1f3 fix: updated icon for cmux module (#531)
## Description

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

## Type of Change

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

## Module Information

**Path:** `registry/coder/modules/cmux`  
**New version:** `v1.0.1`  
**Breaking change:** [ ] Yes [ ] No

## Testing & Validation

- [x] Tests pass (`bun test`)
- [x] Code formatted (`bun fmt`)
- [x] Changes tested locally
2025-11-10 15:22:55 +02:00
Michael Suchacz b8bde9bf12 feat: add cmux module (#523)
## Description

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

## Type of Change

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

## Module Information

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

**Path:** `registry/[namespace]/modules/[module-name]`  
**New version:** `v1.0.0`  
**Breaking change:** [ ] Yes [ ] No

## Template Information

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

**Path:** `registry/[namespace]/templates/[template-name]`

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2025-11-06 15:58:10 -05:00
Danielle Maywood 7249e902ea refactor(coder/amazon-q): support terraform provider coder 2.12.0 (#489)
## Description

Updates the module to use the new version of the agentapi module for the
upcoming Coder 2.28 release

## Type of Change

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

## Module Information

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

**Path:** `registry/coder/modules/amazon-q`  
**New version:** `v3.0.0`  
**Breaking change:** [x] Yes [ ] No

## Testing & Validation

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

## Related Issues

- https://github.com/coder/internal/issues/1065

## Related PRs

- https://github.com/coder/registry/pull/485

Co-authored-by: Cian Johnston <cian@coder.com>
2025-11-03 16:37:54 +00:00
Danielle Maywood 99e51bd365 refactor(coder/goose): support terraform provider coder v2.12.0 (#490)
## Description

Updates the module to use the new version of the agentapi module for the
upcoming Coder 2.28 release

## Type of Change

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

## Module Information

**Path:** `registry/coder/modules/goose`  
**New version:** `v1.0.0`  
**Breaking change:** [x] Yes [ ] No

## Testing & Validation

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

## Related Issues

- https://github.com/coder/internal/issues/1065

## Related PRs

- https://github.com/coder/registry/pull/485

Co-authored-by: Cian Johnston <cian@coder.com>
2025-11-03 16:32:06 +00:00
Danielle Maywood ff02249128 refactor(coder/claude-code): support terraform provider coder 2.12.0 (#488)
## Description

Updates the module to use the new version of the agentapi module for the
upcoming Coder 2.28 release

## Type of Change

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

## Module Information

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

## Testing & Validation

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

## Related Issues

- https://github.com/coder/internal/issues/1065

## Related PRs

- https://github.com/coder/registry/pull/485
- https://github.com/coder/registry/pull/497

Co-authored-by: Cian Johnston <cian@coder.com>
2025-11-03 16:26:08 +00:00
djarbz 4a11b06cba Fix/djarbz copyparty argcommas (#516)
## Description

I discovered that if we included a comma inside an argument that bash
would split it out as a separate argument.
I added a test to verify.
I also cleaned up some log formatting.

## Type of Change

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

## Module Information

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

**Path:** `registry/djarbz/modules/copyparty`  
**New version:** `v1.0.1`  
**Breaking change:** [ ] Yes [x] No

## Testing & Validation

- [N/A] Tests pass (`bun test`)
- [x] Code formatted (`bun fmt`)
- [x] Changes tested locally

## Related Issues

None

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-10-31 10:04:07 -05:00
DevCats 925c71e641 fix: improve version extraction logic to prevent false positives (#511)
## Description

Makes the version extract and replace logic more specific so it wont
replace any field that does is not specifically `field` under the
detected modules.
<!-- Briefly describe what this PR does and why -->

## Type of Change

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

## Testing & Validation

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

## Related Issues

#510 
<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: Atif Ali <atif@coder.com>
2025-10-31 07:45:52 -05:00
Atif Ali 5450113939 fix(coder/modules/claude-code): move set -euo pipefail after sourcing .bashrc (#520)
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
2025-10-30 20:17:33 +05:00
uzair-coder07 0ce65b2b58 fix(coder-labs/modules/sourcegraph-amp): explicitly require external provider (#519)
Co-authored-by: Atif Ali <atif@coder.com>
2025-10-30 10:28:52 +05:00
Yevhenii Shcherbina 92ab526733 feat: change boundary rules according to new spec (#517) 2025-10-29 19:57:15 -04:00
Rhys Williams d6d0101f09 Fix Devolutions Auto-Complete (#508)
## Description

I’ve completed a set of modifications to improve the user experience and
session behaviour within Devolutions Gateway:

- Auto-Complete Fix: Resolved issues with auto-complete functionality.
- Container Visibility: Implemented logic to hide the app-net-scan
container, preventing it from displaying during the initial session
load.
- Default Settings: Enabled Unicode keyboard mode and dynamic window
resizing by default to enhance usability.
- Session Closure Behaviour: Modified the "Close Session" button to
fully close the session window, avoiding returns to the session manager.
- Dynamic Module Path Construction: Refactored the PowerShell module
path setup to be dynamically constructed.
- Input Variables: Added `slug` and `display_name` as input variables.

## Type of Change

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

## Module Information

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

**Path:** `registry/coder/modules/windows-rdp`  
**New version:** `v1.3.0`  
**Breaking change:** [ ] Yes [x] No

## Testing & Validation

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

## Related Issues

"None"

---------

Co-authored-by: DevCats <christofer@coder.com>
Co-authored-by: DevelopmentCats <chris@dualriver.com>
Co-authored-by: Eric Paulsen <ericpaulsen@coder.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-28 10:00:41 +00:00
Luis 1a15ad650a Update Vault CLI download link to use architecture (#514)
## Description

The download command was downloading only the amd64 version,

## Type of Change

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

## Module Information

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

**Path:** `registry/[namespace]/modules/[module-name]`  
**New version:** `v1.0.0`  
**Breaking change:** [ ] Yes [ ] No

## Template Information

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

**Path:** `registry/[namespace]/templates/[template-name]`

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2025-10-27 17:12:24 -05:00
Atif Ali d64851774b fix(jetbrains): update Terraform version requirement to 1.9+ (#513)
## Summary

- Updated `required_version` constraint from `>= 1.0` to `>= 1.9` in
jetbrains module
- Added inline comment explaining the cross-variable validation
requirement
- Bumped module version from `1.1.0` to `1.1.1` (patch version)

## Issue

The jetbrains module uses cross-variable validation at line 169-171
where `var.options` is referenced within the `var.ide_config` validation
block:

```tf
validation {
  condition = alltrue([
    for code in var.options : contains(keys(var.ide_config), code)
  ])
  error_message = "The ide_config must be a superset of var.options."
}
```

This pattern requires Terraform 1.9+ and fails on earlier versions with:
```
Error: Invalid reference in variable validation
The condition for variable "ide_config" can only refer to the variable itself, using var.ide_config.
```

## References

- Terrafomr release blog that talks abut this feature:
https://www.hashicorp.com/en/blog/terraform-1-9-enhances-input-variable-validations
- Terraform PR that added this feature:
https://github.com/hashicorp/terraform/pull/34955
- HashiCorp Support Article:
https://support.hashicorp.com/hc/en-us/articles/43291233547027

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DevCats <christofer@coder.com>
2025-10-27 08:36:19 -05:00
DevCats d3b40c08f1 feat: add session resumption to codex (#506)
## Description

Add continue variable, and logic for resuming task sessions 
<!-- Briefly describe what this PR does and why -->

## Type of Change

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

## Module Information

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

**Path:** `registry/coder-labs/modules/codex`  
**New version:** `v3.1.0`  
**Breaking change:** [ ] Yes [X] No

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2025-10-27 07:45:37 -05:00
Yevhenii Shcherbina 01f5100068 fix: drop perms for boundary process (#512) 2025-10-24 21:23:42 -04:00
Yevhenii Shcherbina 7e42a145fa feat: dropping perms before running claude (#509)
Co-authored-by: DevCats <christofer@coder.com>
Co-authored-by: Atif Ali <atif@coder.com>
2025-10-24 15:35:20 -05:00
Atif Ali 0ff3dbcc48 chore(claude-code): limit MCP tools for task reporting (#507) 2025-10-24 23:14:34 +05:00
netsgnut a327e79bc4 fix(kasmvnc): change installed check and bump default version (#505)
## Description

This PR makes the following changes to the `coder/modules/kasmvnc`:
- Change the installation check from checking `vncserver` to
`kasmvncserver`.
- Bump the default KasmVNC installation version to
[1.4.0](https://docs.kasmvnc.com/docs/release_notes/1.4.0).

In images where there is already TightVNC installed, the current
installation check will erroneously report that KasmVNC is already
installed. By checking `kasmvncserver` instead, it ensures KasmVNC is
installed.

Tested on Debian, Kali and Alpine-based images.

## Type of Change

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

## Module Information

**Path:** `registry/coder/modules/kasmvnc`  
**New version:** `v1.2.5`  
**Breaking change:** [ ] Yes [X] No

## Testing & Validation

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

## Related Issues

None
2025-10-24 17:47:51 +00:00
Harsh Singh Panwar bc39c2ee29 Aider module support agentAPI (#356)
Closes #239

/claim #239

## Description

video :-
https://www.loom.com/share/d1d1d54d48bc45c4a48271ca9a387a88?sid=933e250d-78f8-4a7f-9745-0e908c0ee4d9
<!-- Briefly describe what this PR does and why -->

## Type of Change

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

## Module Information

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

**Path:** `registry/coder/modules/aider`  
**New version:** `v1.0.0`  
**Breaking change:** [ ] Yes [x] No

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-10-24 10:25:40 -05:00
Danielle Maywood e3ff43c0a6 refactor(coder/agentapi): support terraform-provider-coder v2.12.0 (#485)
In terraform-provider-coder v2.12.0 and the
up-coming coder v2.28 release we have removed the
requirement for the "AI Prompt" parameter, and are intending on slightly
re-designing the API of the AI task modules.

Instead of `agentapi` defining the `coder_ai_task` resource, it will
output the `task_app_id`. Consumers of the module will then be expected
to create the `coder_ai_task` resource themselves with this
`task_app_id`.
2025-10-24 11:54:12 +01:00
Yevhenii Shcherbina 30123e7ea3 feat: add boundary pprof server in claude-code module (#503) 2025-10-23 14:18:30 -04:00
djarbz f7c1be71f7 Add [copyparty] module (#486)
## Description

This PR adds a module to install Copyparty as an alternative to
Filebrowser.

## Type of Change

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

## Module Information

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

**Path:** `registry/djarbz/modules/copyparty`  
**New version:** `v0.1.0`  
**Breaking change:** [ ] Yes [x] No

## Testing & Validation

- [N/A] Tests pass (`bun test`)
- [x] Code formatted (`bun fmt`)
- [x] Changes tested locally

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
None

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-10-23 11:19:05 -05:00
DevCats 19519a0a13 fix: add shebang to zed coder_script (#504)
## Description

Add `#!/bin/sh` to zed_settings coder_script
<!-- Briefly describe what this PR does and why -->

## Type of Change

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

## Module Information

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

**Path:** `registry/coder/modules/zed`  
**New version:** `v1.1.1`  
**Breaking change:** [ ] Yes [X] No

## Testing & Validation

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

## Related Issues

https://github.com/coder/registry/issues/482
<!-- Link related issues or write "None" if not applicable -->
2025-10-23 07:39:27 -05:00
Rowan Smith 63e42283ce chore: Update templates from Always to IfNotPresent for image_pull_policy (#501)
## Description

Change `image_pull_policy` from `Always` to `IfNotPresent` on Coder
owned templates. Given these are a reference point for users and
customers and they copy them into their own templates I think it makes
sense to encourage the use of caching of images.

## Type of Change

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

## Template Information

**Path:** 


https://github.com/coder/registry/tree/main/registry/coder/templates/kubernetes-devcontainer

https://github.com/coder/registry/tree/main/registry/coder/templates/kubernetes-envbox

https://github.com/coder/registry/tree/main/registry/coder/templates/kubernetes

## Testing & Validation

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

## Related Issues

None
2025-10-23 04:28:58 +00:00
Eric Paulsen 0c5a8a2354 add nfs-deployment template (#502)
## Description

this PR adds a new template to the registry, which shows how to mount an
NFS share to a K8s deployment workspace.

## Type of Change

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

## Template Information

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

**Path:** `registry/ericpaulsen/templates/nfs-deployment`

## Testing & Validation

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

## Related Issues

None

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-10-22 11:51:58 -05:00
DevCats 51ec6e3212 fix: resolve issues with claude-code session resumption (#496)
## Description

Fixes session resumption logic by having the continue flag decide
whether to continue a workspace based on session history

## Type of Change

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

## Module Information

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

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

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-22 10:58:01 -05:00
DevCats 843b1f1e5a chore: change copilot default version to latest (#499)
## Description

Changes `copilot_version` default to `latest`

## Type of Change

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

## Module Information

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

**Path:** `registry/coder-labs/modules/copilot`  
**New version:** `v0.2.2`  
**Breaking change:** [ ] Yes [X] No

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2025-10-22 07:33:09 -05:00
Jiachen Jiang 583918bfef added example of boundary to claude code module (#500) 2025-10-21 16:33:15 -05:00
Benjamin Peinhardt a1786a09ea update claude-code module version (#498)
The version for the claude-code module should have been updated in
https://github.com/coder/registry/pull/455. This PR updates the module
version so we can cut a release 😎
2025-10-21 13:46:32 -05:00
Benjamin Peinhardt a35986d7df feat: initial boundary integration with claude code (#455)
Closes #

## Description

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

## Type of Change

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

## Module Information

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

**Path:** `registry/[namespace]/modules/[module-name]`  
**New version:** `v1.0.0`  
**Breaking change:** [ ] Yes [ ] No

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: YEVHENII SHCHERBINA <yevhenii@coder.com>
2025-10-21 13:44:26 -04:00
Mathias Fredriksson e34320cb0b feat: add archive module (#422)
This change adds a new `archive` module to the Coder registry. It can be
used to archive user-data from pre-defined locations and restore it as
well.

Here we also explore:

- A new method of passing arrays from Terraform to Bash
- A new method of writing Bash scripts that minimizes the interaction
with terraform interpolation
- Extensive test-suite that not only tests that Terraform options can be
selected, but also the resulting script behaviors

---------

Co-authored-by: Cian Johnston <cian@coder.com>
Co-authored-by: DevCats <christofer@coder.com>
2025-10-17 08:14:56 -05:00
35C4n0r ca7bc42946 feat: update auth setup in codex (#472)
Closes #

## Description

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

## Type of Change

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

## Module Information

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

**Path:** `registry/coder-labs/modules/codex`  
**New version:** `v3.0.0`  
**Breaking change:** [X] Yes [ ] No

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: DevCats <christofer@coder.com>
2025-10-16 15:25:57 -05:00
35C4n0r a599302774 feat: amp upgrades for better ux (#390)
Closes #

## Description
- remove default node installation
- users can pass amp versions now
- move env variables to terraform variable (system prompt and ai prompt)

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

## Type of Change

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

## Module Information

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

**Path:** `registry/coder-labs/modules/sourcegraph-amp`  
**New version:** `v2.0.0`  
**Breaking change:** [x] Yes [ ] No

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->

---------

Co-authored-by: DevCats <christofer@coder.com>
Co-authored-by: Atif Ali <me@matifali.dev>
2025-10-16 15:21:17 -05:00
DevCats ff09c415e8 feat: change tf test and validation to use paths-filter (#483)
## Description

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

## Type of Change

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

## Module Information

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

**Path:** `registry/[namespace]/modules/[module-name]`  
**New version:** `v1.0.0`  
**Breaking change:** [ ] Yes [ ] No

## Template Information

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

**Path:** `registry/[namespace]/templates/[template-name]`

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2025-10-16 14:21:03 -05:00
DevCats 90873e8009 ci: update CI workflow to run TypeScript tests with new script (#480) 2025-10-15 14:03:12 -05:00
DevCats 2168360195 fix: add folder to all Agent Modules (#481)
## Description

Make sure folder is passed to agentapi in all Agent modules.
<!-- Briefly describe what this PR does and why -->

## Type of Change

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

## Module Information

**Path:** `registry/coder-labs/modules/auggie`  
**New version:** `v0.2.1`  
**Breaking change:** [ ] Yes [X] No

**Path:** `registry/coder-labs/modules/cursor-cli`  
**New version:** `v0.2.1`  
**Breaking change:** [ ] Yes [X] No

**Path:** `registry/coder-labs/modules/gemini`  
**New version:** `v2.1.1`  
**Breaking change:** [ ] Yes [X] No

**Path:** `registry/coder-labs/modules/sourcegraph_amp`  
**New version:** `v1.1.1`  
**Breaking change:** [ ] Yes [X] No

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2025-10-15 12:20:08 -05:00
Riajul Islam da5a2ba6a8 feat(git-clone module): added post_clone_script. (#357)
Co-authored-by: DevCats <christofer@coder.com>
Co-authored-by: Atif Ali <atif@coder.com>
2025-10-15 12:53:17 +00:00
Anas 63cad25954 fix(amazon-q): pass workdir variable into agentapi folder variable (#478)
Co-authored-by: DevCats <christofer@coder.com>
2025-10-15 17:44:58 +05:00
Hulto cd759bd9a1 goose module: pass folder along to agentapi (#412)
Co-authored-by: DevCats <christofer@coder.com>
2025-10-15 17:44:18 +05:00
DevCats 54a7bb0001 docs: add usage examples for bedrock and vertex (#431)
Closes #

## Description

Adds Usage Examples for Vertex and Bedrock as described in the linked
documentation.

## Type of Change

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

## Module Information

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

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

## Testing & Validation

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

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
2025-10-14 12:06:32 -05:00
Matt Hazinski 50f4d5388b fix(codex): pass folder variable to agentapi module (#477)
## Description

The folder variable was not being passed from the codex module to the
agentapi module, causing agentapi to use its default value of
`/home/coder` instead of the user-specified folder path.

This resulted in permission errors when the codex module tried to create
directories in `/home/coder` when users specified a different folder
like `/home/matt/foo`.

Fix by adding `folder = var.folder` to the agentapi module invocation.

## Type of Change

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

## Module Information

Path: registry/coder-labs/modules/codex
New version: v2.1.1
Breaking change: [ ] Yes [X] No

## Testing & Validation

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

## Related Issues
Fixes https://github.com/coder/registry/issues/476

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: DevelopmentCats <christofer@coder.com>
2025-10-14 16:49:52 +00:00
245 changed files with 13210 additions and 2469 deletions
@@ -82,7 +82,8 @@ create_incident() {
# Function to check for existing unresolved incidents
check_existing_incident() {
# Fetch the latest incidents with status not equal to "RESOLVED"
local unresolved_incidents=$(curl -s -X GET "https://api.instatus.com/v1/$INSTATUS_PAGE_ID/incidents" \
local unresolved_incidents
unresolved_incidents=$(curl -s -X GET "https://api.instatus.com/v1/$INSTATUS_PAGE_ID/incidents" \
-H "Authorization: Bearer $INSTATUS_API_KEY" \
-H "Content-Type: application/json" | jq -r '.incidents[] | select(.status != "RESOLVED") | .id')
+72 -23
View File
@@ -1,14 +1,18 @@
#!/bin/bash
# Version Bump Script
# Usage: ./version-bump.sh <bump_type> [base_ref]
# Usage: ./version-bump.sh [--ci] <bump_type> [base_ref]
# --ci: CI mode - run bump, check for changes, exit 1 if changes needed
# bump_type: patch, minor, or major
# base_ref: base reference for diff (default: origin/main)
set -euo pipefail
CI_MODE=false
usage() {
echo "Usage: $0 <bump_type> [base_ref]"
echo "Usage: $0 [--ci] <bump_type> [base_ref]"
echo " --ci: CI mode - validates versions are already bumped (exits 1 if not)"
echo " bump_type: patch, minor, or major"
echo " base_ref: base reference for diff (default: origin/main)"
echo ""
@@ -16,6 +20,7 @@ usage() {
echo " $0 patch # Update versions with patch bump"
echo " $0 minor # Update versions with minor bump"
echo " $0 major # Update versions with major bump"
echo " $0 --ci patch # CI check: verify patch bump has been applied"
exit 1
}
@@ -70,23 +75,43 @@ update_readme_version() {
if grep -q "source.*${module_source}" "$readme_path"; then
echo "Updating version references for $namespace/$module_name in $readme_path"
awk -v module_source="$module_source" -v new_version="$new_version" '
/source.*=.*/ {
if ($0 ~ module_source) {
in_target_module = 1
} else {
in_target_module = 0
}
/^[[:space:]]*module[[:space:]]/ {
in_module_block = 1
module_content = $0 "\n"
module_has_target_source = 0
next
}
/version.*=.*"/ {
if (in_target_module) {
gsub(/version[[:space:]]*=[[:space:]]*"[^"]*"/, "version = \"" new_version "\"")
in_target_module = 0
in_module_block {
module_content = module_content $0 "\n"
if ($0 ~ /source.*=/ && $0 ~ module_source) {
module_has_target_source = 1
}
if ($0 ~ /^[[:space:]]*}[[:space:]]*$/) {
in_module_block = 0
if (module_has_target_source) {
num_lines = split(module_content, lines, "\n")
for (i = 1; i < num_lines; i++) {
line = lines[i]
if (line ~ /^[[:space:]]*version[[:space:]]*=/) {
match(line, /^[[:space:]]*/)
indent = substr(line, 1, RLENGTH)
printf "%sversion = \"%s\"\n", indent, new_version
} else {
print line
}
}
} else {
printf "%s", module_content
}
module_content = ""
next
}
next
}
{ print }
' "$readme_path" > "${readme_path}.tmp" && mv "${readme_path}.tmp" "$readme_path"
return 0
elif grep -q 'version\s*=\s*"' "$readme_path"; then
elif grep -q '^[[:space:]]*version[[:space:]]*=' "$readme_path"; then
echo "⚠️ Found version references but no module source match for $namespace/$module_name"
return 1
fi
@@ -95,6 +120,11 @@ update_readme_version() {
}
main() {
if [ "${1:-}" = "--ci" ]; then
CI_MODE=true
shift
fi
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
usage
fi
@@ -132,6 +162,8 @@ main() {
local untagged_modules=""
local has_changes=false
declare -a modified_readme_files=()
while IFS= read -r module_path; do
if [ -z "$module_path" ]; then continue; fi
@@ -148,9 +180,9 @@ main() {
local current_version
if [ -z "$latest_tag" ]; then
if [ -f "$readme_path" ] && grep -q 'version\s*=\s*"' "$readme_path"; then
if [ -f "$readme_path" ] && grep -q '^[[:space:]]*version[[:space:]]*=' "$readme_path"; then
local readme_version
readme_version=$(grep 'version\s*=\s*"' "$readme_path" | head -1 | sed 's/.*version\s*=\s*"\([^"]*\)".*/\1/')
readme_version=$(awk '/^[[:space:]]*version[[:space:]]*=/ { match($0, /"[^"]*"/); print substr($0, RSTART+1, RLENGTH-2); exit }' "$readme_path")
echo "No git tag found, but README shows version: $readme_version"
if ! validate_version "$readme_version"; then
@@ -182,6 +214,7 @@ main() {
if update_readme_version "$readme_path" "$namespace" "$module_name" "$new_version"; then
updated_readmes="$updated_readmes\n- $namespace/$module_name"
modified_readme_files+=("$readme_path")
has_changes=true
fi
@@ -190,19 +223,22 @@ main() {
done <<< "$modules"
# Always run formatter to ensure consistent formatting
echo "🔧 Running formatter to ensure consistent formatting..."
if command -v bun > /dev/null 2>&1; then
bun fmt > /dev/null 2>&1 || echo "⚠️ Warning: bun fmt failed, but continuing..."
else
echo "⚠️ Warning: bun not found, skipping formatting"
if [ ${#modified_readme_files[@]} -gt 0 ]; then
echo "🔧 Formatting modified README files..."
if command -v bun > /dev/null 2>&1; then
for readme_file in "${modified_readme_files[@]}"; do
bun run prettier --write "$readme_file" 2> /dev/null || true
done
else
echo "⚠️ Warning: bun not found, skipping formatting"
fi
echo ""
fi
echo ""
echo "📋 Summary:"
echo "Bump Type: $bump_type"
echo ""
echo "Modules Updated:"
echo "Modules Processed:"
echo -e "$bumped_modules"
echo ""
@@ -219,6 +255,19 @@ main() {
echo ""
fi
if [ "$CI_MODE" = true ]; then
echo "🔍 Comparing files to committed versions..."
if git diff --quiet; then
echo "✅ PASS: All versions match - no changes needed"
exit 0
else
echo "❌ FAIL: Module versions need to be updated"
echo ""
echo "Run './.github/scripts/version-bump.sh $bump_type' locally and commit the changes"
exit 1
fi
fi
if [ "$has_changes" = true ]; then
echo "✅ Version bump completed successfully!"
echo "📝 README files have been updated with new versions."
+3
View File
@@ -3,8 +3,11 @@ muc = "muc" # For Munich location code
tyo = "tyo" # For Tokyo location code
Hashi = "Hashi"
HashiCorp = "HashiCorp"
hel = "hel" # For Helsinki location code
mavrickrishi = "mavrickrishi" # Username
mavrick = "mavrick" # Username
inh = "inh" # Option in setpriv command
exportfs = "exportfs" # nfs related binary
[files]
extend-exclude = ["registry/coder/templates/aws-devcontainer/architecture.svg"] #False positive
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Run check.sh
run: |
+50 -7
View File
@@ -12,7 +12,30 @@ 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
with:
list-files: shell
filters: |
shared:
- 'test/**'
- 'package.json'
- 'bun.lock'
- 'bunfig.toml'
- 'tsconfig.json'
- '.github/workflows/ci.yaml'
- 'scripts/ts_test_auto.sh'
- 'scripts/terraform_test_all.sh'
- 'scripts/terraform_validate.sh'
- 'scripts/shellcheck_validate.sh'
modules:
- 'registry/**/modules/**'
shell:
- '**/*.sh'
all:
- '**'
- name: Set up Terraform
uses: coder/coder/.github/actions/setup-tf@main
- name: Set up Bun
@@ -27,17 +50,37 @@ jobs:
- name: Install dependencies
run: bun install
- name: Run TypeScript tests
run: bun test
env:
ALL_CHANGED_FILES: ${{ steps.filter.outputs.all_files }}
SHARED_CHANGED: ${{ steps.filter.outputs.shared }}
MODULE_CHANGED_FILES: ${{ steps.filter.outputs.modules_files }}
run: bun tstest
- name: Run Terraform tests
run: ./scripts/terraform_test_all.sh
env:
ALL_CHANGED_FILES: ${{ steps.filter.outputs.all_files }}
SHARED_CHANGED: ${{ steps.filter.outputs.shared }}
MODULE_CHANGED_FILES: ${{ steps.filter.outputs.modules_files }}
run: bun tftest
- name: Run Terraform Validate
env:
ALL_CHANGED_FILES: ${{ steps.filter.outputs.all_files }}
SHARED_CHANGED: ${{ steps.filter.outputs.shared }}
MODULE_CHANGED_FILES: ${{ steps.filter.outputs.modules_files }}
run: bun terraform-validate
- name: Run ShellCheck
env:
ALL_CHANGED_FILES: ${{ steps.filter.outputs.all_files }}
SHARED_CHANGED: ${{ steps.filter.outputs.shared }}
SHELL_CHANGED_FILES: ${{ steps.filter.outputs.shell_files }}
run: bun shellcheck
- name: Validate set -u ordering
run: ./scripts/validate_set_u_order.sh
validate-style:
name: Check for typos and unformatted code
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:
@@ -50,7 +93,7 @@ jobs:
- name: Validate formatting
run: bun fmt:ci
- name: Check for typos
uses: crate-ci/typos@v1.38.1
uses: crate-ci/typos@v1.40.0
with:
config: .github/typos.toml
validate-readme-files:
@@ -61,11 +104,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
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Authenticate with Google Cloud
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093
with:
+2 -2
View File
@@ -14,11 +14,11 @@ 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
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
uses: golangci/golangci-lint-action@v9
with:
version: v2.1
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
+23 -50
View File
@@ -20,7 +20,7 @@ jobs:
issues: write
steps:
- name: Checkout code
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -55,62 +55,35 @@ jobs:
;;
esac
- name: Check version bump requirements
id: version-check
run: |
output_file=$(mktemp)
if ./.github/scripts/version-bump.sh "${{ steps.bump-type.outputs.type }}" origin/main > "$output_file" 2>&1; then
echo "Script completed successfully"
else
echo "Script failed"
cat "$output_file"
exit 1
fi
- name: Check version bump
run: ./.github/scripts/version-bump.sh --ci "${{ steps.bump-type.outputs.type }}" origin/main
{
echo "output<<EOF"
cat "$output_file"
echo "EOF"
} >> $GITHUB_OUTPUT
cat "$output_file"
if git diff --quiet; then
echo "versions_up_to_date=true" >> $GITHUB_OUTPUT
echo "✅ All module versions are already up to date"
else
echo "versions_up_to_date=false" >> $GITHUB_OUTPUT
echo "❌ Module versions need to be updated"
echo "Files that would be changed:"
git diff --name-only
echo ""
echo "Diff preview:"
git diff
git checkout .
git clean -fd
exit 1
fi
- name: Comment on PR - Failure
if: failure() && steps.version-check.outputs.versions_up_to_date == 'false'
- name: Comment on PR - Version bump required
if: failure()
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `${{ steps.version-check.outputs.output }}`;
const bumpType = `${{ steps.bump-type.outputs.type }}`;
let comment = `## ❌ Version Bump Validation Failed\n\n`;
comment += `**Bump Type:** \`${bumpType}\`\n\n`;
comment += `Module versions need to be updated but haven't been bumped yet.\n\n`;
comment += `**Required Actions:**\n`;
comment += `1. Run the version bump script locally: \`./.github/scripts/version-bump.sh ${bumpType}\`\n`;
comment += `2. Commit the changes: \`git add . && git commit -m "chore: bump module versions (${bumpType})"\`\n`;
comment += `3. Push the changes: \`git push\`\n\n`;
comment += `### Script Output:\n\`\`\`\n${output}\n\`\`\`\n\n`;
comment += `> Please update the module versions and push the changes to continue.`;
const comment = [
'## Version Bump Required',
'',
'One or more modules in this PR need their versions updated.',
'',
'**To fix this:**',
'1. Run the version bump script locally:',
' ```bash',
` ./.github/scripts/version-bump.sh ${bumpType}`,
' ```',
'2. Commit the changes:',
' ```bash',
` git add . && git commit -m "chore: bump module versions (${bumpType})"`,
' ```',
'3. Push your changes',
'',
'The CI will automatically re-run once you push the updated versions.'
].join('\n');
github.rest.issues.createComment({
issue_number: context.issue.number,
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.3 MiB

+47
View File
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1024" height="1024">
<path d="M0 0 C337.92 0 675.84 0 1024 0 C1024 337.92 1024 675.84 1024 1024 C686.08 1024 348.16 1024 0 1024 C0 686.08 0 348.16 0 0 Z " fill="#020708" transform="translate(0,0)"/>
<path d="M0 0 C40.22460379 0 80.4466421 0.00304941 120.67057991 0.15463066 C125.95372492 0.17442425 131.23687063 0.19398844 136.52001762 0.21324348 C147.77834068 0.25435249 159.03666181 0.29592484 170.29497623 0.33933926 C170.99953161 0.34205531 171.70408698 0.34477135 172.42999252 0.34756971 C173.13533008 0.3502892 173.84066764 0.35300869 174.56737906 0.35581058 C175.99454502 0.36131272 177.42171098 0.36681401 178.84887695 0.37231445 C179.55666117 0.37504276 180.26444539 0.37777106 180.99367761 0.38058204 C192.50410753 0.42485496 204.01454726 0.46550406 215.52499563 0.50465261 C227.56090905 0.54564378 239.59680781 0.58979401 251.63269895 0.63687855 C258.30394122 0.66290814 264.97518119 0.68769341 271.64644051 0.70907402 C277.91414179 0.72924498 284.18181589 0.75352717 290.4494915 0.78049088 C292.69376491 0.78952735 294.93804399 0.79730645 297.18232727 0.80341911 C322.46351662 0.87417207 347.71815265 1.5042775 373 2 C373 96.05 373 190.1 373 287 C249.91 287 126.82 287 0 287 C0 192.29 0 97.58 0 0 Z " fill="#999F9E" transform="translate(326,395)"/>
<path d="M0 0 C37.29 0 74.58 0 113 0 C113 46.86 113 93.72 113 142 C75.71 142 38.42 142 0 142 C0 95.14 0 48.28 0 0 Z " fill="#181E23" transform="translate(550,445)"/>
<path d="M0 0 C37.29 0 74.58 0 113 0 C113 46.53 113 93.06 113 141 C95.613125 141.0309375 95.613125 141.0309375 77.875 141.0625 C74.3054248 141.071604 70.73584961 141.08070801 67.05810547 141.09008789 C62.46899414 141.09460449 62.46899414 141.09460449 60.27615356 141.09544373 C58.84198693 141.09691347 57.40782093 141.10027703 55.97366333 141.10557556 C37.3007381 141.17069509 18.67741441 140.49151091 0 140 C0 93.8 0 47.6 0 0 Z " fill="#181E23" transform="translate(362,446)"/>
<path d="M0 0 C0 14.52 0 29.04 0 44 C-118.14 44 -236.28 44 -358 44 C-359.71802382 40.56395235 -359.1196618 36.61561104 -359.09765625 32.8359375 C-359.0962413 31.92872955 -359.09482635 31.02152161 -359.09336853 30.08682251 C-359.08776096 27.18284845 -359.07520718 24.27895085 -359.0625 21.375 C-359.05748598 19.40885528 -359.0529229 17.44270935 -359.04882812 15.4765625 C-359.03778906 10.6510121 -359.02051935 5.82551903 -359 1 C-319.04215869 0.87185681 -279.08431526 0.74439427 -239.12646896 0.61781773 C-234.3862332 0.60280127 -229.64599745 0.58777929 -224.90576172 0.57275391 C-223.49049118 0.56826798 -223.49049118 0.56826798 -222.04662931 0.56369144 C-206.8611473 0.51554559 -191.67566674 0.46695532 -176.49018669 0.41819459 C-160.85514345 0.36800044 -145.22009869 0.3182983 -129.58505249 0.26903296 C-120.82560112 0.24141872 -112.06615054 0.21359008 -103.30670166 0.18519592 C-68.87102254 0.07367311 -34.43592726 -0.03058253 0 0 Z " fill="#FEFEFE" transform="translate(692,838)"/>
<path d="M0 0 C117.15 0 234.3 0 355 0 C355 13.86 355 27.72 355 42 C237.85 42 120.7 42 0 42 C0 28.14 0 14.28 0 0 Z " fill="#FEFEFE" transform="translate(334,135)"/>
<path d="M0 0 C11.88 0 23.76 0 36 0 C36 19.8 36 39.6 36 60 C91.44 60 146.88 60 204 60 C204 71.55 204 83.1 204 95 C168.85263573 95.02994071 133.70661685 94.95253395 98.55956407 94.80645666 C91.61749391 94.77764681 84.67541903 94.75005412 77.7333438 94.72249681 C65.36625264 94.67337442 52.99916523 94.62336685 40.63208008 94.57275391 C28.66105336 94.52376386 16.69002497 94.47522104 4.71899414 94.42724609 C3.97369616 94.4242591 3.22839818 94.4212721 2.46051542 94.41819459 C-1.2806363 94.4032035 -5.02178805 94.38822085 -8.76293981 94.37324238 C-39.50863188 94.25013489 -70.25431782 94.12555501 -101 94 C-101 93.67 -101 93.34 -101 93 C-134.66 92.505 -134.66 92.505 -169 92 C-169 81.44 -169 70.88 -169 60 C-113.23 60 -57.46 60 0 60 C0 40.2 0 20.4 0 0 Z " fill="#191F24" transform="translate(495,302)"/>
<path d="M0 0 C62.7 0 125.4 0 190 0 C190 11.88 190 23.76 190 36 C127.3 36 64.6 36 0 36 C0 24.12 0 12.24 0 0 Z " fill="#474B51" transform="translate(418,786)"/>
<path d="M0 0 C49.5 0 99 0 150 0 C150 14.52 150 29.04 150 44 C100.5 44 51 44 0 44 C0 29.48 0 14.96 0 0 Z " fill="#FDFEFE" transform="translate(200,239)"/>
<path d="M0 0 C48.18 0 96.36 0 146 0 C146 14.52 146 29.04 146 44 C131.979151 44.02742613 117.95836252 44.05097148 103.9375 44.0625 C102.90375118 44.06335602 101.87000237 44.06421204 100.80492783 44.06509399 C67.19004349 44.08969196 33.60574878 43.83234053 0 43 C0 28.81 0 14.62 0 0 Z " fill="#FEFEFE" transform="translate(677,239)"/>
<path d="M0 0 C51.48 0 102.96 0 156 0 C156 13.53 156 27.06 156 41 C104.52 41 53.04 41 0 41 C0 27.47 0 13.94 0 0 Z " fill="#FEFEFE" transform="translate(259,188)"/>
<path d="M0 0 C1.28729507 -0.00281982 2.57459015 -0.00563965 3.90089417 -0.00854492 C5.34078993 -0.00660032 6.78068557 -0.00455213 8.22058105 -0.00241089 C9.72647647 -0.00376487 11.23237154 -0.00554479 12.73826599 -0.00772095 C16.839391 -0.01229564 20.94049097 -0.01050558 25.04161644 -0.00734186 C29.32396896 -0.00481844 33.60631927 -0.00715575 37.88867188 -0.00872803 C45.08150621 -0.01054978 52.2743303 -0.00814327 59.46716309 -0.00338745 C67.79516627 0.00205518 76.12314497 0.00029253 84.45114756 -0.00521386 C91.58828139 -0.00974483 98.72540783 -0.01039561 105.86254263 -0.00777519 C110.13098648 -0.00621233 114.39941881 -0.00601889 118.66786194 -0.00931168 C122.67984227 -0.01219017 126.69179089 -0.01021511 130.70376778 -0.00441742 C132.18045054 -0.0030897 133.65713595 -0.00348413 135.13381767 -0.00568008 C137.1412128 -0.0083911 139.14861372 -0.0043972 141.15600586 0 C142.28205986 0.00037703 143.40811386 0.00075405 144.56829071 0.0011425 C147.07800293 0.12698364 147.07800293 0.12698364 148.07800293 1.12698364 C148.07800293 14.32698364 148.07800293 27.52698364 148.07800293 41.12698364 C96.92800293 41.12698364 45.77800293 41.12698364 -6.92199707 41.12698364 C-6.92199707 0.00231762 -6.92199707 0.00231762 0 0 Z " fill="#FEFEFE" transform="translate(616.9219970703125,187.87301635742188)"/>
<path d="M0 0 C43.23 0 86.46 0 131 0 C131 15.84 131 31.68 131 48 C87.77 48 44.54 48 0 48 C0 32.16 0 16.32 0 0 Z " fill="#FEFEFE" transform="translate(128,353)"/>
<path d="M0 0 C67.815 0.495 67.815 0.495 137 1 C137 15.19 137 29.38 137 44 C136.34 44 135.68 44 135 44 C135 44.66 135 45.32 135 46 C90.45 46 45.9 46 0 46 C0 30.82 0 15.64 0 0 Z " fill="#FDFEFE" transform="translate(156,294)"/>
<path d="M0 0 C45.21 0 90.42 0 137 0 C137 14.19 137 28.38 137 43 C136.34 43 135.68 43 135 43 C135 43.66 135 44.32 135 45 C90.45 45 45.9 45 0 45 C0 30.15 0 15.3 0 0 Z " fill="#FBFCFC" transform="translate(732,295)"/>
<path d="M0 0 C43.23 0 86.46 0 131 0 C131 15.18 131 30.36 131 46 C98.474264 46.72107728 65.97062256 47.09604795 33.4375 47.0625 C32.60141457 47.06164398 31.76532913 47.06078796 30.90390778 47.05990601 C20.60257228 47.04857042 10.30132061 47.02553343 0 47 C0 31.49 0 15.98 0 0 Z " fill="#FEFEFE" transform="translate(765,354)"/>
<path d="M0 0 C26.73 0 53.46 0 81 0 C82.19877676 50.94801223 82.19877676 50.94801223 82 74 C77.05167364 74.80762419 72.35065215 75.12200785 67.32055664 75.11352539 C66.57629897 75.11364593 65.8320413 75.11376646 65.06523037 75.11389065 C62.68017418 75.11315027 60.29519828 75.10556237 57.91015625 75.09765625 C56.42801959 75.09618411 54.9458824 75.09516508 53.46374512 75.09460449 C47.99665543 75.08938309 42.52957691 75.07542183 37.0625 75.0625 C24.831875 75.041875 12.60125 75.02125 0 75 C0 50.25 0 25.5 0 0 Z " fill="#FB4740" transform="translate(473,226)"/>
<path d="M0 0 C36.96 0 73.92 0 112 0 C112.6261198 6.88731776 113.15084094 13.48218949 113.1328125 20.3515625 C113.13376923 21.17707611 113.13472595 22.00258972 113.13571167 22.8531189 C113.1363846 24.57216469 113.13459319 26.29121317 113.13037109 28.01025391 C113.12494037 30.65402778 113.13038111 33.29763707 113.13671875 35.94140625 C113.13605916 37.61979204 113.13478047 39.29817773 113.1328125 40.9765625 C113.13483673 41.76809723 113.13686096 42.55963196 113.13894653 43.37515259 C113.11499765 48.88500235 113.11499765 48.88500235 112 50 C110.54518505 50.0956161 109.08574514 50.12188351 107.62779236 50.12025452 C106.68403244 50.12162918 105.74027252 50.12300385 104.76791382 50.12442017 C103.72422638 50.12082489 102.68053894 50.11722961 101.60522461 50.11352539 C100.51267868 50.11367142 99.42013275 50.11381744 98.29447937 50.1139679 C94.66374767 50.11326822 91.03306654 50.10547329 87.40234375 50.09765625 C84.89270557 50.09579226 82.38306702 50.09436827 79.87342834 50.09336853 C73.93065407 50.08993348 67.98789616 50.08204273 62.04513031 50.07201904 C53.43224996 50.0578058 44.81936343 50.05254739 36.2064743 50.04621124 C24.13763437 50.03649856 12.06884226 50.01734306 0 50 C0 33.5 0 17 0 0 Z " fill="#FDFDFD" transform="translate(117,474)"/>
<path d="M0 0 C36.63 0 73.26 0 111 0 C111 48 111 48 110 50 C73.7 50 37.4 50 0 50 C0 33.5 0 17 0 0 Z " fill="#FEFEFE" transform="translate(795,474)"/>
<path d="M0 0 C54.12 0 108.24 0 164 0 C164 10.89 164 21.78 164 33 C109.88 33 55.76 33 0 33 C0 22.11 0 11.22 0 0 Z " fill="#181E23" transform="translate(431,622)"/>
<path d="M0 0 C14.58571527 -0.0225479 29.17141907 -0.04091327 43.75714779 -0.05181217 C50.52910421 -0.05697491 57.30104905 -0.06401718 64.07299805 -0.07543945 C70.60224426 -0.08644714 77.13148294 -0.09227625 83.66073799 -0.09487724 C86.15793612 -0.09673199 88.65513355 -0.10035354 91.15232658 -0.10573006 C94.63664069 -0.11293684 98.12090431 -0.1139911 101.60522461 -0.11352539 C102.64891205 -0.11712067 103.69259949 -0.12071594 104.76791382 -0.12442017 C105.71167374 -0.1230455 106.65543365 -0.12167084 107.62779236 -0.12025452 C108.45279708 -0.12117631 109.2778018 -0.1220981 110.12780666 -0.12304783 C112 0 112 0 113 1 C113 16.18 113 31.36 113 47 C75.71 47 38.42 47 0 47 C0 31.49 0 15.98 0 0 Z " fill="#FCFCFC" transform="translate(117,414)"/>
<path d="M0 0 C0 15.84 0 31.68 0 48 C-36.96 48 -73.92 48 -112 48 C-112 32.49 -112 16.98 -112 1 C-92.15950898 0.75043408 -92.15950898 0.75043408 -83.5078125 0.64453125 C-77.67218771 0.57308961 -71.83656856 0.50132395 -66.00097656 0.42724609 C-43.99861261 0.14838814 -22.0044946 -0.06103882 0 0 Z " fill="#FAFAFA" transform="translate(906,537)"/>
<path d="M0 0 C36.63 0 73.26 0 111 0 C111 15.51 111 31.02 111 47 C74.37 47 37.74 47 0 47 C0 31.49 0 15.98 0 0 Z " fill="#FBFCFC" transform="translate(118,538)"/>
<path d="M0 0 C36.3 0 72.6 0 110 0 C110 15.18 110 30.36 110 46 C86.98227311 46.96572558 64.03031552 47.12476904 40.99664307 47.06866455 C36.24190736 47.05823048 31.48716586 47.05382501 26.73242188 47.04882812 C17.48826669 47.03826689 8.24413623 47.02131845 -1 47 C-1.02529825 40.62793828 -1.04284496 34.25588946 -1.05493164 27.88378906 C-1.05996891 25.71483883 -1.06679891 23.545892 -1.07543945 21.37695312 C-1.08753151 18.26431437 -1.09323428 15.1517204 -1.09765625 12.0390625 C-1.10281754 11.065065 -1.10797882 10.0910675 -1.11329651 9.08755493 C-1.11337204 8.18677216 -1.11344757 7.28598938 -1.11352539 6.35791016 C-1.115746 5.56293121 -1.11796661 4.76795227 -1.12025452 3.94888306 C-1 2 -1 2 0 0 Z " fill="#FBFCFB" transform="translate(795,414)"/>
<path d="M0 0 C34.98 0 69.96 0 106 0 C106 15.84 106 31.68 106 48 C71.02 48 36.04 48 0 48 C0 32.16 0 16.32 0 0 Z " fill="#FBFCFC" transform="translate(154,658)"/>
<path d="M0 0 C34.32 0 68.64 0 104 0 C104 15.84 104 31.68 104 48 C69.35 48 34.7 48 -1 48 C-1 31.99652815 -0.6951464 15.9883671 0 0 Z " fill="#FBFBFB" transform="translate(765,658)"/>
<path d="M0 0 C10.56252046 -0.02735617 21.1249617 -0.05092969 31.6875 -0.0625 C32.46348038 -0.06335602 33.23946075 -0.06421204 34.03895569 -0.06509399 C57.71503621 -0.08818645 81.33439347 0.16962784 105 1 C105 16.18 105 31.36 105 47 C70.35 47 35.7 47 0 47 C0 31.49 0 15.98 0 0 Z " fill="#FCFDFD" transform="translate(125,598)"/>
<path d="M0 0 C34.32 0 68.64 0 104 0 C104 15.51 104 31.02 104 47 C69.68 47 35.36 47 0 47 C0 31.49 0 15.98 0 0 Z " fill="#FBFCFC" transform="translate(794,598)"/>
<path d="M0 0 C37.62 0 75.24 0 114 0 C114 12.54 114 25.08 114 38 C76.38 38 38.76 38 0 38 C0 25.46 0 12.92 0 0 Z " fill="#474B51" transform="translate(456,716)"/>
<path d="M0 0 C30.69 0 61.38 0 93 0 C93 15.18 93 30.36 93 46 C62.31 46 31.62 46 0 46 C0 30.82 0 15.64 0 0 Z " fill="#FCFDFD" transform="translate(732,719)"/>
<path d="M0 0 C30.69 0 61.38 0 93 0 C93 14.85 93 29.7 93 45 C62.31 45 31.62 45 0 45 C0 30.15 0 15.3 0 0 Z " fill="#FEFEFE" transform="translate(200,720)"/>
<path d="M0 0 C11.22 0 22.44 0 34 0 C34.6943434 30.12954393 35.09747148 60.23833134 35.0625 90.375 C35.06121597 91.48948643 35.06121597 91.48948643 35.05990601 92.62648773 C35.04860423 101.7510267 35.02558276 110.87548153 35 120 C23.45 120 11.9 120 0 120 C0 80.4 0 40.8 0 0 Z " fill="#474B50" transform="translate(258,470)"/>
<path d="M0 0 C30.36 0 60.72 0 92 0 C92 14.19 92 28.38 92 43 C61.64 43 31.28 43 0 43 C0 28.81 0 14.62 0 0 Z " fill="#FCFDFD" transform="translate(259,781)"/>
<path d="M0 0 C30.03 0 60.06 0 91 0 C91 14.19 91 28.38 91 43 C60.97 43 30.94 43 0 43 C0 28.81 0 14.62 0 0 Z " fill="#FCFDFD" transform="translate(674,781)"/>
<path d="M0 0 C16.5 0 33 0 50 0 C50 25.41 50 50.82 50 77 C33.5 77 17 77 0 77 C0 51.59 0 26.18 0 0 Z " fill="#2CCDD4" transform="translate(582,478)"/>
<path d="M0 0 C10.56 0 21.12 0 32 0 C32 39.27 32 78.54 32 119 C21.44 119 10.88 119 0 119 C0 79.73 0 40.46 0 0 Z " fill="#474B50" transform="translate(732,471)"/>
<path d="M0 0 C16.17 0 32.34 0 49 0 C49 25.41 49 50.82 49 77 C32.83 77 16.66 77 0 77 C0 51.59 0 26.18 0 0 Z " fill="#2DCCD2" transform="translate(394,478)"/>
<path d="M0 0 C15.84 0 31.68 0 48 0 C48 0.33 48 0.66 48 1 C32.49 1 16.98 1 1 1 C1 25.75 1 50.5 1 76 C28.06 75.67 55.12 75.34 83 75 C82.67 65.76 82.34 56.52 82 47 C81.93702338 38.74754429 81.90172306 30.50164687 81.9375 22.25 C81.94254243 20.16145945 81.94710032 18.07291767 81.95117188 15.984375 C81.96192066 10.98955155 81.97902162 5.99479052 82 1 C82.33 1 82.66 1 83 1 C83.57831229 19.95510799 84.11107346 38.91589618 84.23217773 57.88024902 C84.24403757 59.45720133 84.26110851 61.03412324 84.28344727 62.61096191 C84.31307026 64.8085447 84.32331211 67.00536665 84.328125 69.203125 C84.3374707 70.44739258 84.34681641 71.69166016 84.35644531 72.97363281 C84 76 84 76 82.73034668 77.68478394 C80.56608158 79.32981626 79.63802574 79.28920572 76.95800781 79.06982422 C75.71830231 78.98657394 75.71830231 78.98657394 74.45355225 78.90164185 C73.54591125 78.82839691 72.63827026 78.75515198 71.703125 78.6796875 C62.53186675 78.09888659 53.42327768 77.92843278 44.234375 78.0078125 C42.339151 78.0199881 42.339151 78.0199881 40.40563965 78.03240967 C35.17784805 78.06788344 29.95020985 78.1100683 24.72265625 78.17138672 C20.85162217 78.21602536 16.98061225 78.23702797 13.109375 78.2578125 C11.32536285 78.28574387 11.32536285 78.28574387 9.50531006 78.3142395 C7.87024506 78.31942093 7.87024506 78.31942093 6.20214844 78.32470703 C4.76279938 78.33922409 4.76279938 78.33922409 3.29437256 78.35403442 C2.15865814 78.17878738 2.15865814 78.17878738 1 78 C-1.11766624 74.82350064 -1.24885987 74.11349575 -1.23046875 70.48828125 C-1.22917969 69.52510986 -1.22789062 68.56193848 -1.2265625 67.56958008 C-1.21367187 66.49474365 -1.20078125 65.41990723 -1.1875 64.3125 C-1.18105469 63.18158936 -1.17460937 62.05067871 -1.16796875 60.88549805 C-1.00069226 40.5888483 -0.4546929 20.29067045 0 0 Z " fill="#4C0F0C" transform="translate(472,225)"/>
<path d="M0 0 C8.45159814 -0.02350035 16.90318192 -0.04110214 25.35480499 -0.05181217 C29.28212158 -0.05696064 33.20941531 -0.06390567 37.13671875 -0.07543945 C53.56520732 -0.12238185 69.94755695 -0.05605073 86.36132812 0.74389648 C96.40041688 1.22706882 106.45208983 1.36454744 116.5 1.5625 C118.60677558 1.60589009 120.71354645 1.64950917 122.8203125 1.69335938 C127.88015843 1.79819282 132.94005639 1.8999973 138 2 C138 2.33 138 2.66 138 3 C92.79 3 47.58 3 1 3 C1 5.64 1 8.28 1 11 C0.67 11 0.34 11 0 11 C0 7.37 0 3.74 0 0 Z " fill="#6F7576" transform="translate(326,395)"/>
<path d="M0 0 C0.33 0 0.66 0 1 0 C1 7.26 1 14.52 1 22 C37.3 22 73.6 22 111 22 C111.495 22.99 111.495 22.99 112 24 C74.71 24 37.42 24 -1 24 C-0.67 16.08 -0.34 8.16 0 0 Z " fill="#0E1418" transform="translate(551,563)"/>
<path d="M0 0 C0.33 0 0.66 0 1 0 C1.57831229 18.95510799 2.11107346 37.91589618 2.23217773 56.88024902 C2.24403757 58.45720133 2.26110851 60.03412324 2.28344727 61.61096191 C2.31307026 63.8085447 2.32331211 66.00536665 2.328125 68.203125 C2.3374707 69.44739258 2.34681641 70.69166016 2.35644531 71.97363281 C2 75 2 75 0.79858398 76.92016602 C-1.65515594 78.39334257 -3.26106965 78.2550765 -6.10546875 78.07421875 C-7.08837891 78.01943359 -8.07128906 77.96464844 -9.08398438 77.90820312 C-10.62022461 77.79895508 -10.62022461 77.79895508 -12.1875 77.6875 C-13.22326172 77.62626953 -14.25902344 77.56503906 -15.32617188 77.50195312 C-17.88531957 77.34864013 -20.44267045 77.1807893 -23 77 C-23.33 76.34 -23.66 75.68 -24 75 C-11.625 74.505 -11.625 74.505 1 74 C0.835 70.32875 0.67 66.6575 0.5 62.875 C-0.33621628 41.9290255 -0.08802477 20.95802133 0 0 Z " fill="#390D0B" transform="translate(554,226)"/>
<path d="M0 0 C0.66 0 1.32 0 2 0 C2 15.18 2 30.36 2 46 C0.68 45.67 -0.64 45.34 -2 45 C-1.88269497 38.75188831 -1.75785931 32.50396027 -1.62768555 26.25610352 C-1.58426577 24.12885993 -1.54259607 22.0015799 -1.50268555 19.87426758 C-1.44515293 16.82361849 -1.38141033 13.77315411 -1.31640625 10.72265625 C-1.29969376 9.76537125 -1.28298126 8.80808624 -1.26576233 7.8217926 C-1.24581711 6.94095505 -1.22587189 6.06011749 -1.20532227 5.15258789 C-1.18977798 4.37315323 -1.1742337 3.59371857 -1.15821838 2.79066467 C-1 1 -1 1 0 0 Z " fill="#D1D3D3" transform="translate(228,599)"/>
<path d="M0 0 C35.64 0.33 71.28 0.66 108 1 C108 1.33 108 1.66 108 2 C54.54 2.495 54.54 2.495 0 3 C0 2.01 0 1.02 0 0 Z " fill="#F5F7F7" transform="translate(117,521)"/>
<path d="M0 0 C6.93 0 13.86 0 21 0 C21 0.99 21 1.98 21 3 C14.07 3 7.14 3 0 3 C0 2.01 0 1.02 0 0 Z " fill="#272A2A" transform="translate(158,292)"/>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

+210
View File
@@ -0,0 +1,210 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="300mm"
height="207mm"
viewBox="0 0 300 207"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<title
id="title1">copyparty_logo</title>
<defs
id="defs1">
<linearGradient
inkscape:collect="always"
id="linearGradient1">
<stop
style="stop-color:#ffcc55;stop-opacity:1"
offset="0"
id="stop1" />
<stop
style="stop-color:#ffcc00;stop-opacity:1"
offset="0.2"
id="stop2" />
<stop
style="stop-color:#ff8800;stop-opacity:1"
offset="1"
id="stop3" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient1"
id="linearGradient2"
x1="15"
y1="15"
x2="15"
y2="143"
gradientUnits="userSpaceOnUse" />
</defs>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>copyparty_logo</dc:title>
<dc:source>github.com/9001/copyparty</dc:source>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="kassett">
<rect
style="fill:#333333"
id="rect1"
width="300"
height="205"
x="0"
y="0"
rx="12"
ry="12" />
<rect
style="fill:url(#linearGradient2)"
id="rect2"
width="270"
height="128"
x="15"
y="15"
rx="8"
ry="8" />
<rect
style="fill:#333333"
id="rect3"
width="172"
height="52"
x="64"
y="72"
rx="26"
ry="26" />
<circle
style="fill:#cccccc"
id="circle1"
cx="91"
cy="98"
r="18" />
<circle
style="fill:#cccccc"
id="circle2"
cx="209"
cy="98"
r="18" />
<path
style="fill:#737373;stroke-width:1px"
d="m 48,207 10,-39 c 1.79,-6.2 5.6,-7.8 12,-8 60,-1 100,-1 160,0 6.4,0.2 10,1.8 12,8 l 10,39 z"
id="path1"
sodipodi:nodetypes="ccccccc" />
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="tekst"
style="display:none">
<text
xml:space="preserve"
style="font-size:38.8056px;line-height:1.25;font-family:Akbar;-inkscape-font-specification:Akbar;letter-spacing:3.70417px;word-spacing:0px;fill:#333333"
x="47.153069"
y="55.548954"
id="text1"><tspan
sodipodi:role="line"
id="tspan1"
x="47.153069"
y="55.548954"
style="-inkscape-font-specification:Akbar"
rotate="0 0">copyparty</tspan></text>
</g>
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="stensatt">
<path
d="m 63.5,50.9 q -0.85,0.93 -4.73,2.3 -3.6,1.3 -4.4,1.3 -3.3,0 -5.1,-2.1 -1.75,-2 -1.75,-5.36 0,-4.6 3.76,-7.64 3.3,-2.7 7.3,-2.7 0.4,0 0.93,0.74 0.54,0.7 0.54,1.16 0,2.06 -2.2,2.7 -1.36,0.4 -4.04,1.16 -2.2,1.16 -2.2,4.4 0,3.2 2.9,3.2 0.85,0 0.85,0 0.54,0 1.44,-0.16 1.1,-0.23 2.9,-0.74 1.8,-0.54 2.13,-0.54 0.4,0 1.75,0.6 z"
style="fill:#333333"
id="path11" />
<path
d="m 87.6,45 q 0,4.2 -3.7,6.95 -3.2,2.3 -6.87,2.3 -3.4,0 -6,-2.6 -2.5,-2.6 -2.5,-6 0,-3.6 3.14,-6.64 3.2,-3 6.8,-3 3.5,0 6.3,2.76 2.83,2.76 2.83,6.25 z m -3.4,0.16 q 0,-2.25 -1.75,-3.7 -1.7,-1.5 -4,-1.5 -0.1,0 -1.6,1.6 -1.44,1.55 -2.44,1.55 -0.6,0 -0.8,-0.3 -1.16,2.3 -1.16,3 0,2.25 2.13,3.4 1.6,0.9 3.6,0.9 2,0 3.76,-1.1 2.25,-1.4 2.25,-3.84 z"
style="fill:#333333"
id="path12" />
<path
d="m 112.8,46.8 q 0,2.8 -1.9,4.4 -1.8,1.5 -4.7,1.5 -0.7,0 -2.7,-0.4 -1.9,-0.4 -2.6,-0.4 -2.1,0 -2.1,2.64 0,0.85 0.23,2.6 0.2,1.75 0.2,2.6 0,1.9 -0.77,2.83 -1.44,0 -3,-0.85 -1.46,-9.5 -1.46,-12 0,-3.65 1.75,-8.1 2.37,-6.05 6.45,-6.05 3.7,0 7.3,4.1 3.3,3.84 3.3,7.14 z m -3.8,0.2 q -0.6,-2.2 -2.6,-4.4 -2.3,-2.5 -4.3,-2.5 -1.3,0 -2.33,2.2 -0.9,1.8 -0.9,3.26 0,0.47 0.38,1.24 0.43,0.8 0.85,0.8 1.1,0 3.2,0.3 2.1,0.3 3.2,0.3 0.3,0 1.3,-0.4 1,-0.47 1.3,-0.74 z"
style="fill:#333333"
id="path13" />
<path
d="m 133,40 q -2.1,4.1 -3.2,7 -0.1,0.3 -1.6,4.5 -0.4,1.36 -1,4.2 -0.5,2.83 -1,4.2 -1,2.83 -2.3,2.64 -1.4,-0.2 -1.6,-1.6 0,-0.2 0,-0.5 0,-0.16 0.3,-1.5 1,-5.04 1,-6.44 0,-0.54 -0.1,-0.74 -1.4,-2.44 -4.1,-7.4 -2.7,-4.97 -2.4,-7.7 1.5,-1.36 2.1,-1.36 0.4,0 1.1,0.6 0.6,0.6 0.7,1.1 0.8,6.2 4.9,11.1 1,-1.8 1.8,-4.04 0.5,-1.4 1.6,-4.15 1.9,-4.46 3.4,-4.46 0.2,0 0.4,0.1 0.9,0.3 1.3,2.8 z"
style="fill:#333333"
id="path14" />
<path
d="m 157.5,48 q 0,2.8 -1.9,4.4 -1.8,1.5 -4.7,1.5 -0.7,0 -2.7,-0.4 -1.9,-0.4 -2.6,-0.4 -2,0 -2,2.64 0,0.85 0.2,2.6 0.2,1.75 0.2,2.6 0,1.9 -0.7,2.83 -1.5,0 -3,-0.85 -1.5,-9.5 -1.5,-11.95 0,-3.65 1.8,-8.1 2.3,-6.05 6.4,-6.05 3.7,0 7.2,4.1 3.3,3.84 3.3,7.14 z m -3.8,0.2 q -0.6,-2.2 -2.6,-4.4 -2.3,-2.5 -4.3,-2.5 -1.3,0 -2.3,2.2 -0.9,1.8 -0.9,3.26 0,0.47 0.4,1.24 0.4,0.8 0.8,0.8 1.1,0 3.2,0.3 2.1,0.3 3.2,0.3 0.3,0 1.3,-0.4 1,-0.47 1.3,-0.74 z"
style="fill:#333333"
id="path15" />
<path
d="m 182,53.3 q 0,0.9 -0.6,1.5 -0.6,0.6 -1.4,0.6 -1.6,0 -3,-0.9 -1.4,-0.93 -2.1,-2.3 -0.7,-0.1 -1.5,0.85 -0.9,1.16 -1.1,1.24 -1.2,0.54 -3.9,0.54 -2.2,0 -3.9,-2.44 -1.5,-2.13 -1.5,-4 0,-3.4 3.4,-6.4 3.2,-2.9 6.7,-2.9 0.9,0 1.7,0.6 0.8,0.6 0.8,1.44 0,0.54 -0.4,1.1 2.4,0.9 2.4,2.83 0,0.35 -0.1,1.05 -0.1,0.7 -0.1,1.05 0,0.4 0.1,0.6 0.5,1.3 2.5,3.4 1.9,1.9 1.9,2.2 z m -8.1,-10.1 q -0.4,0 -1.1,-0.1 -0.8,-0.16 -1.1,-0.16 -1.3,0 -3.2,1.94 -1.9,1.94 -1.9,3.3 0,0.8 0.7,1.8 0.9,1.3 2.2,1.3 2.6,0 3.5,-2.9 0.5,-2.6 1,-5.16 z"
style="fill:#333333"
id="path16" />
<path
d="m 203.8,42.4 q -0.4,0.4 -1.5,0.4 -0.9,0 -2.5,-0.3 -1.7,-0.3 -2.5,-0.3 -4.7,0 -5.5,6.9 -0.3,3.1 -0.4,3.3 -0.4,1 -1.7,2.3 h -1.1 q -0.7,-1.2 -1.3,-4.1 -0.6,-2.76 -0.6,-4.27 0,-1.16 0.1,-1.5 0.2,-0.54 1,-0.54 0.3,0 0.6,0.3 0.4,0.3 0.4,0.3 1.9,-3.53 3.1,-4.6 1.8,-1.7 5.1,-1.7 1.4,0 3.6,0.9 2.8,1.16 3.3,2.8 z"
style="fill:#333333"
id="path17" />
<path
d="m 229.5,37.16 q 0.3,0.8 0.3,1.44 0,1.86 -2.4,1.86 -1,0 -3.5,-0.5 -2.5,-0.54 -3.4,-0.54 -1.3,0 -1.5,0.1 -0.4,0.2 -0.4,1.2 0,2.2 0.6,6.9 0.7,5.86 1.6,6.13 -0.4,0.35 -0.4,1.1 -1.2,0.7 -2.6,0.7 -1.4,0 -2,-3.9 -0.2,-1.36 -0.5,-7.76 -0.2,-4.6 -0.8,-5.5 -0.3,-0.47 -4.3,-0.35 -1,0 -1.6,0.1 -0.5,0 -0.3,0 -0.8,0 -1.2,-0.7 -0.5,-1.3 -0.5,-1.4 0,-1.44 4.1,-2 1.6,-0.16 4.7,-0.5 0,-0.85 -0.1,-2.56 0,-1.75 0,-2.6 0,-4.35 2.1,-4.35 0.5,0 1.1,0.6 0.6,0.6 0.6,1.1 v 7.9 q 1.1,1.2 5,1.7 3.9,0.5 5.3,1.86 z"
style="fill:#333333"
id="path18" />
<path
d="m 251.2,40.2 q -2,4.1 -3.2,7 -0.1,0.3 -1.5,4.5 -0.5,1.36 -1,4.2 -0.5,2.83 -1,4.2 -1,2.83 -2.4,2.64 -1.4,-0.2 -1.5,-1.6 -0.1,-0.2 -0.1,-0.5 0,-0.16 0.3,-1.5 1.1,-5.04 1.1,-6.44 0,-0.54 -0.1,-0.74 -1.4,-2.44 -4.1,-7.4 -2.7,-4.97 -2.4,-7.7 1.4,-1.36 2.1,-1.36 0.4,0 1,0.6 0.6,0.6 0.7,1.1 0.9,6.2 4.9,11.1 1,-1.8 1.9,-4.04 0.5,-1.4 1.6,-4.15 1.8,-4.46 3.4,-4.46 0.2,0 0.4,0.1 0.8,0.3 1.2,2.8 z"
style="fill:#333333"
id="path19" />
</g>
<g
inkscape:groupmode="layer"
id="layer5"
inkscape:label="tagger">
<g
id="g1">
<path
id="path4"
style="fill:#333333"
d="m 111.4,83.335 -9.526,5.5 2.5,4.33 9.526,-5.5 z m -33.775,19.5 -9.526,5.5 2.5,4.33 9.526,-5.5 z"
sodipodi:nodetypes="cccccccccc" />
<path
id="path5"
style="fill:#333333"
d="M 88.5,73 V 84 h 5 V 73 Z m 0,39 v 11 h 5 V 112 Z"
sodipodi:nodetypes="cccccccccc" />
<path
id="path6"
style="fill:#333333"
d="m 68.1,87.665 9.526,5.5 2.5,-4.33 -9.526,-5.5 z m 33.775,19.5 9.527,5.5 2.5,-4.33 -9.527,-5.5 z"
sodipodi:nodetypes="cccccccccc" />
</g>
<g
id="g2"
transform="rotate(30,150,318.19)">
<path
id="path7"
style="fill:#333333"
d="m 111.4,83.335 -9.526,5.5 2.5,4.33 9.526,-5.5 z m -33.775,19.5 -9.526,5.5 2.5,4.33 9.526,-5.5 z"
sodipodi:nodetypes="cccccccccc" />
<path
id="path8"
style="fill:#333333"
d="M 88.5,73 V 84 h 5 V 73 Z m 0,39 v 11 h 5 V 112 Z"
sodipodi:nodetypes="cccccccccc" />
<path
id="path9"
style="fill:#333333"
d="m 68.1,87.665 9.526,5.5 2.5,-4.33 -9.526,-5.5 z m 33.775,19.5 9.527,5.5 2.5,-4.33 -9.527,-5.5 z"
sodipodi:nodetypes="cccccccccc" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.3 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#FFF"><path d="M7.05 40q-1.2 0-2.1-.925-.9-.925-.9-2.075V11q0-1.15.9-2.075Q5.85 8 7.05 8h14l3 3h17q1.15 0 2.075.925.925.925.925 2.075v23q0 1.15-.925 2.075Q42.2 40 41.05 40Zm0-29v26h34V14H22.8l-3-3H7.05Zm0 0v26Z"/></svg>

After

Width:  |  Height:  |  Size: 289 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.5 KiB

+47
View File
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="1024" height="1024">
<path d="M0 0 C337.92 0 675.84 0 1024 0 C1024 337.92 1024 675.84 1024 1024 C686.08 1024 348.16 1024 0 1024 C0 686.08 0 348.16 0 0 Z " fill="#020708" transform="translate(0,0)"/>
<path d="M0 0 C40.22460379 0 80.4466421 0.00304941 120.67057991 0.15463066 C125.95372492 0.17442425 131.23687063 0.19398844 136.52001762 0.21324348 C147.77834068 0.25435249 159.03666181 0.29592484 170.29497623 0.33933926 C170.99953161 0.34205531 171.70408698 0.34477135 172.42999252 0.34756971 C173.13533008 0.3502892 173.84066764 0.35300869 174.56737906 0.35581058 C175.99454502 0.36131272 177.42171098 0.36681401 178.84887695 0.37231445 C179.55666117 0.37504276 180.26444539 0.37777106 180.99367761 0.38058204 C192.50410753 0.42485496 204.01454726 0.46550406 215.52499563 0.50465261 C227.56090905 0.54564378 239.59680781 0.58979401 251.63269895 0.63687855 C258.30394122 0.66290814 264.97518119 0.68769341 271.64644051 0.70907402 C277.91414179 0.72924498 284.18181589 0.75352717 290.4494915 0.78049088 C292.69376491 0.78952735 294.93804399 0.79730645 297.18232727 0.80341911 C322.46351662 0.87417207 347.71815265 1.5042775 373 2 C373 96.05 373 190.1 373 287 C249.91 287 126.82 287 0 287 C0 192.29 0 97.58 0 0 Z " fill="#999F9E" transform="translate(326,395)"/>
<path d="M0 0 C37.29 0 74.58 0 113 0 C113 46.86 113 93.72 113 142 C75.71 142 38.42 142 0 142 C0 95.14 0 48.28 0 0 Z " fill="#181E23" transform="translate(550,445)"/>
<path d="M0 0 C37.29 0 74.58 0 113 0 C113 46.53 113 93.06 113 141 C95.613125 141.0309375 95.613125 141.0309375 77.875 141.0625 C74.3054248 141.071604 70.73584961 141.08070801 67.05810547 141.09008789 C62.46899414 141.09460449 62.46899414 141.09460449 60.27615356 141.09544373 C58.84198693 141.09691347 57.40782093 141.10027703 55.97366333 141.10557556 C37.3007381 141.17069509 18.67741441 140.49151091 0 140 C0 93.8 0 47.6 0 0 Z " fill="#181E23" transform="translate(362,446)"/>
<path d="M0 0 C0 14.52 0 29.04 0 44 C-118.14 44 -236.28 44 -358 44 C-359.71802382 40.56395235 -359.1196618 36.61561104 -359.09765625 32.8359375 C-359.0962413 31.92872955 -359.09482635 31.02152161 -359.09336853 30.08682251 C-359.08776096 27.18284845 -359.07520718 24.27895085 -359.0625 21.375 C-359.05748598 19.40885528 -359.0529229 17.44270935 -359.04882812 15.4765625 C-359.03778906 10.6510121 -359.02051935 5.82551903 -359 1 C-319.04215869 0.87185681 -279.08431526 0.74439427 -239.12646896 0.61781773 C-234.3862332 0.60280127 -229.64599745 0.58777929 -224.90576172 0.57275391 C-223.49049118 0.56826798 -223.49049118 0.56826798 -222.04662931 0.56369144 C-206.8611473 0.51554559 -191.67566674 0.46695532 -176.49018669 0.41819459 C-160.85514345 0.36800044 -145.22009869 0.3182983 -129.58505249 0.26903296 C-120.82560112 0.24141872 -112.06615054 0.21359008 -103.30670166 0.18519592 C-68.87102254 0.07367311 -34.43592726 -0.03058253 0 0 Z " fill="#FEFEFE" transform="translate(692,838)"/>
<path d="M0 0 C117.15 0 234.3 0 355 0 C355 13.86 355 27.72 355 42 C237.85 42 120.7 42 0 42 C0 28.14 0 14.28 0 0 Z " fill="#FEFEFE" transform="translate(334,135)"/>
<path d="M0 0 C11.88 0 23.76 0 36 0 C36 19.8 36 39.6 36 60 C91.44 60 146.88 60 204 60 C204 71.55 204 83.1 204 95 C168.85263573 95.02994071 133.70661685 94.95253395 98.55956407 94.80645666 C91.61749391 94.77764681 84.67541903 94.75005412 77.7333438 94.72249681 C65.36625264 94.67337442 52.99916523 94.62336685 40.63208008 94.57275391 C28.66105336 94.52376386 16.69002497 94.47522104 4.71899414 94.42724609 C3.97369616 94.4242591 3.22839818 94.4212721 2.46051542 94.41819459 C-1.2806363 94.4032035 -5.02178805 94.38822085 -8.76293981 94.37324238 C-39.50863188 94.25013489 -70.25431782 94.12555501 -101 94 C-101 93.67 -101 93.34 -101 93 C-134.66 92.505 -134.66 92.505 -169 92 C-169 81.44 -169 70.88 -169 60 C-113.23 60 -57.46 60 0 60 C0 40.2 0 20.4 0 0 Z " fill="#191F24" transform="translate(495,302)"/>
<path d="M0 0 C62.7 0 125.4 0 190 0 C190 11.88 190 23.76 190 36 C127.3 36 64.6 36 0 36 C0 24.12 0 12.24 0 0 Z " fill="#474B51" transform="translate(418,786)"/>
<path d="M0 0 C49.5 0 99 0 150 0 C150 14.52 150 29.04 150 44 C100.5 44 51 44 0 44 C0 29.48 0 14.96 0 0 Z " fill="#FDFEFE" transform="translate(200,239)"/>
<path d="M0 0 C48.18 0 96.36 0 146 0 C146 14.52 146 29.04 146 44 C131.979151 44.02742613 117.95836252 44.05097148 103.9375 44.0625 C102.90375118 44.06335602 101.87000237 44.06421204 100.80492783 44.06509399 C67.19004349 44.08969196 33.60574878 43.83234053 0 43 C0 28.81 0 14.62 0 0 Z " fill="#FEFEFE" transform="translate(677,239)"/>
<path d="M0 0 C51.48 0 102.96 0 156 0 C156 13.53 156 27.06 156 41 C104.52 41 53.04 41 0 41 C0 27.47 0 13.94 0 0 Z " fill="#FEFEFE" transform="translate(259,188)"/>
<path d="M0 0 C1.28729507 -0.00281982 2.57459015 -0.00563965 3.90089417 -0.00854492 C5.34078993 -0.00660032 6.78068557 -0.00455213 8.22058105 -0.00241089 C9.72647647 -0.00376487 11.23237154 -0.00554479 12.73826599 -0.00772095 C16.839391 -0.01229564 20.94049097 -0.01050558 25.04161644 -0.00734186 C29.32396896 -0.00481844 33.60631927 -0.00715575 37.88867188 -0.00872803 C45.08150621 -0.01054978 52.2743303 -0.00814327 59.46716309 -0.00338745 C67.79516627 0.00205518 76.12314497 0.00029253 84.45114756 -0.00521386 C91.58828139 -0.00974483 98.72540783 -0.01039561 105.86254263 -0.00777519 C110.13098648 -0.00621233 114.39941881 -0.00601889 118.66786194 -0.00931168 C122.67984227 -0.01219017 126.69179089 -0.01021511 130.70376778 -0.00441742 C132.18045054 -0.0030897 133.65713595 -0.00348413 135.13381767 -0.00568008 C137.1412128 -0.0083911 139.14861372 -0.0043972 141.15600586 0 C142.28205986 0.00037703 143.40811386 0.00075405 144.56829071 0.0011425 C147.07800293 0.12698364 147.07800293 0.12698364 148.07800293 1.12698364 C148.07800293 14.32698364 148.07800293 27.52698364 148.07800293 41.12698364 C96.92800293 41.12698364 45.77800293 41.12698364 -6.92199707 41.12698364 C-6.92199707 0.00231762 -6.92199707 0.00231762 0 0 Z " fill="#FEFEFE" transform="translate(616.9219970703125,187.87301635742188)"/>
<path d="M0 0 C43.23 0 86.46 0 131 0 C131 15.84 131 31.68 131 48 C87.77 48 44.54 48 0 48 C0 32.16 0 16.32 0 0 Z " fill="#FEFEFE" transform="translate(128,353)"/>
<path d="M0 0 C67.815 0.495 67.815 0.495 137 1 C137 15.19 137 29.38 137 44 C136.34 44 135.68 44 135 44 C135 44.66 135 45.32 135 46 C90.45 46 45.9 46 0 46 C0 30.82 0 15.64 0 0 Z " fill="#FDFEFE" transform="translate(156,294)"/>
<path d="M0 0 C45.21 0 90.42 0 137 0 C137 14.19 137 28.38 137 43 C136.34 43 135.68 43 135 43 C135 43.66 135 44.32 135 45 C90.45 45 45.9 45 0 45 C0 30.15 0 15.3 0 0 Z " fill="#FBFCFC" transform="translate(732,295)"/>
<path d="M0 0 C43.23 0 86.46 0 131 0 C131 15.18 131 30.36 131 46 C98.474264 46.72107728 65.97062256 47.09604795 33.4375 47.0625 C32.60141457 47.06164398 31.76532913 47.06078796 30.90390778 47.05990601 C20.60257228 47.04857042 10.30132061 47.02553343 0 47 C0 31.49 0 15.98 0 0 Z " fill="#FEFEFE" transform="translate(765,354)"/>
<path d="M0 0 C26.73 0 53.46 0 81 0 C82.19877676 50.94801223 82.19877676 50.94801223 82 74 C77.05167364 74.80762419 72.35065215 75.12200785 67.32055664 75.11352539 C66.57629897 75.11364593 65.8320413 75.11376646 65.06523037 75.11389065 C62.68017418 75.11315027 60.29519828 75.10556237 57.91015625 75.09765625 C56.42801959 75.09618411 54.9458824 75.09516508 53.46374512 75.09460449 C47.99665543 75.08938309 42.52957691 75.07542183 37.0625 75.0625 C24.831875 75.041875 12.60125 75.02125 0 75 C0 50.25 0 25.5 0 0 Z " fill="#FB4740" transform="translate(473,226)"/>
<path d="M0 0 C36.96 0 73.92 0 112 0 C112.6261198 6.88731776 113.15084094 13.48218949 113.1328125 20.3515625 C113.13376923 21.17707611 113.13472595 22.00258972 113.13571167 22.8531189 C113.1363846 24.57216469 113.13459319 26.29121317 113.13037109 28.01025391 C113.12494037 30.65402778 113.13038111 33.29763707 113.13671875 35.94140625 C113.13605916 37.61979204 113.13478047 39.29817773 113.1328125 40.9765625 C113.13483673 41.76809723 113.13686096 42.55963196 113.13894653 43.37515259 C113.11499765 48.88500235 113.11499765 48.88500235 112 50 C110.54518505 50.0956161 109.08574514 50.12188351 107.62779236 50.12025452 C106.68403244 50.12162918 105.74027252 50.12300385 104.76791382 50.12442017 C103.72422638 50.12082489 102.68053894 50.11722961 101.60522461 50.11352539 C100.51267868 50.11367142 99.42013275 50.11381744 98.29447937 50.1139679 C94.66374767 50.11326822 91.03306654 50.10547329 87.40234375 50.09765625 C84.89270557 50.09579226 82.38306702 50.09436827 79.87342834 50.09336853 C73.93065407 50.08993348 67.98789616 50.08204273 62.04513031 50.07201904 C53.43224996 50.0578058 44.81936343 50.05254739 36.2064743 50.04621124 C24.13763437 50.03649856 12.06884226 50.01734306 0 50 C0 33.5 0 17 0 0 Z " fill="#FDFDFD" transform="translate(117,474)"/>
<path d="M0 0 C36.63 0 73.26 0 111 0 C111 48 111 48 110 50 C73.7 50 37.4 50 0 50 C0 33.5 0 17 0 0 Z " fill="#FEFEFE" transform="translate(795,474)"/>
<path d="M0 0 C54.12 0 108.24 0 164 0 C164 10.89 164 21.78 164 33 C109.88 33 55.76 33 0 33 C0 22.11 0 11.22 0 0 Z " fill="#181E23" transform="translate(431,622)"/>
<path d="M0 0 C14.58571527 -0.0225479 29.17141907 -0.04091327 43.75714779 -0.05181217 C50.52910421 -0.05697491 57.30104905 -0.06401718 64.07299805 -0.07543945 C70.60224426 -0.08644714 77.13148294 -0.09227625 83.66073799 -0.09487724 C86.15793612 -0.09673199 88.65513355 -0.10035354 91.15232658 -0.10573006 C94.63664069 -0.11293684 98.12090431 -0.1139911 101.60522461 -0.11352539 C102.64891205 -0.11712067 103.69259949 -0.12071594 104.76791382 -0.12442017 C105.71167374 -0.1230455 106.65543365 -0.12167084 107.62779236 -0.12025452 C108.45279708 -0.12117631 109.2778018 -0.1220981 110.12780666 -0.12304783 C112 0 112 0 113 1 C113 16.18 113 31.36 113 47 C75.71 47 38.42 47 0 47 C0 31.49 0 15.98 0 0 Z " fill="#FCFCFC" transform="translate(117,414)"/>
<path d="M0 0 C0 15.84 0 31.68 0 48 C-36.96 48 -73.92 48 -112 48 C-112 32.49 -112 16.98 -112 1 C-92.15950898 0.75043408 -92.15950898 0.75043408 -83.5078125 0.64453125 C-77.67218771 0.57308961 -71.83656856 0.50132395 -66.00097656 0.42724609 C-43.99861261 0.14838814 -22.0044946 -0.06103882 0 0 Z " fill="#FAFAFA" transform="translate(906,537)"/>
<path d="M0 0 C36.63 0 73.26 0 111 0 C111 15.51 111 31.02 111 47 C74.37 47 37.74 47 0 47 C0 31.49 0 15.98 0 0 Z " fill="#FBFCFC" transform="translate(118,538)"/>
<path d="M0 0 C36.3 0 72.6 0 110 0 C110 15.18 110 30.36 110 46 C86.98227311 46.96572558 64.03031552 47.12476904 40.99664307 47.06866455 C36.24190736 47.05823048 31.48716586 47.05382501 26.73242188 47.04882812 C17.48826669 47.03826689 8.24413623 47.02131845 -1 47 C-1.02529825 40.62793828 -1.04284496 34.25588946 -1.05493164 27.88378906 C-1.05996891 25.71483883 -1.06679891 23.545892 -1.07543945 21.37695312 C-1.08753151 18.26431437 -1.09323428 15.1517204 -1.09765625 12.0390625 C-1.10281754 11.065065 -1.10797882 10.0910675 -1.11329651 9.08755493 C-1.11337204 8.18677216 -1.11344757 7.28598938 -1.11352539 6.35791016 C-1.115746 5.56293121 -1.11796661 4.76795227 -1.12025452 3.94888306 C-1 2 -1 2 0 0 Z " fill="#FBFCFB" transform="translate(795,414)"/>
<path d="M0 0 C34.98 0 69.96 0 106 0 C106 15.84 106 31.68 106 48 C71.02 48 36.04 48 0 48 C0 32.16 0 16.32 0 0 Z " fill="#FBFCFC" transform="translate(154,658)"/>
<path d="M0 0 C34.32 0 68.64 0 104 0 C104 15.84 104 31.68 104 48 C69.35 48 34.7 48 -1 48 C-1 31.99652815 -0.6951464 15.9883671 0 0 Z " fill="#FBFBFB" transform="translate(765,658)"/>
<path d="M0 0 C10.56252046 -0.02735617 21.1249617 -0.05092969 31.6875 -0.0625 C32.46348038 -0.06335602 33.23946075 -0.06421204 34.03895569 -0.06509399 C57.71503621 -0.08818645 81.33439347 0.16962784 105 1 C105 16.18 105 31.36 105 47 C70.35 47 35.7 47 0 47 C0 31.49 0 15.98 0 0 Z " fill="#FCFDFD" transform="translate(125,598)"/>
<path d="M0 0 C34.32 0 68.64 0 104 0 C104 15.51 104 31.02 104 47 C69.68 47 35.36 47 0 47 C0 31.49 0 15.98 0 0 Z " fill="#FBFCFC" transform="translate(794,598)"/>
<path d="M0 0 C37.62 0 75.24 0 114 0 C114 12.54 114 25.08 114 38 C76.38 38 38.76 38 0 38 C0 25.46 0 12.92 0 0 Z " fill="#474B51" transform="translate(456,716)"/>
<path d="M0 0 C30.69 0 61.38 0 93 0 C93 15.18 93 30.36 93 46 C62.31 46 31.62 46 0 46 C0 30.82 0 15.64 0 0 Z " fill="#FCFDFD" transform="translate(732,719)"/>
<path d="M0 0 C30.69 0 61.38 0 93 0 C93 14.85 93 29.7 93 45 C62.31 45 31.62 45 0 45 C0 30.15 0 15.3 0 0 Z " fill="#FEFEFE" transform="translate(200,720)"/>
<path d="M0 0 C11.22 0 22.44 0 34 0 C34.6943434 30.12954393 35.09747148 60.23833134 35.0625 90.375 C35.06121597 91.48948643 35.06121597 91.48948643 35.05990601 92.62648773 C35.04860423 101.7510267 35.02558276 110.87548153 35 120 C23.45 120 11.9 120 0 120 C0 80.4 0 40.8 0 0 Z " fill="#474B50" transform="translate(258,470)"/>
<path d="M0 0 C30.36 0 60.72 0 92 0 C92 14.19 92 28.38 92 43 C61.64 43 31.28 43 0 43 C0 28.81 0 14.62 0 0 Z " fill="#FCFDFD" transform="translate(259,781)"/>
<path d="M0 0 C30.03 0 60.06 0 91 0 C91 14.19 91 28.38 91 43 C60.97 43 30.94 43 0 43 C0 28.81 0 14.62 0 0 Z " fill="#FCFDFD" transform="translate(674,781)"/>
<path d="M0 0 C16.5 0 33 0 50 0 C50 25.41 50 50.82 50 77 C33.5 77 17 77 0 77 C0 51.59 0 26.18 0 0 Z " fill="#2CCDD4" transform="translate(582,478)"/>
<path d="M0 0 C10.56 0 21.12 0 32 0 C32 39.27 32 78.54 32 119 C21.44 119 10.88 119 0 119 C0 79.73 0 40.46 0 0 Z " fill="#474B50" transform="translate(732,471)"/>
<path d="M0 0 C16.17 0 32.34 0 49 0 C49 25.41 49 50.82 49 77 C32.83 77 16.66 77 0 77 C0 51.59 0 26.18 0 0 Z " fill="#2DCCD2" transform="translate(394,478)"/>
<path d="M0 0 C15.84 0 31.68 0 48 0 C48 0.33 48 0.66 48 1 C32.49 1 16.98 1 1 1 C1 25.75 1 50.5 1 76 C28.06 75.67 55.12 75.34 83 75 C82.67 65.76 82.34 56.52 82 47 C81.93702338 38.74754429 81.90172306 30.50164687 81.9375 22.25 C81.94254243 20.16145945 81.94710032 18.07291767 81.95117188 15.984375 C81.96192066 10.98955155 81.97902162 5.99479052 82 1 C82.33 1 82.66 1 83 1 C83.57831229 19.95510799 84.11107346 38.91589618 84.23217773 57.88024902 C84.24403757 59.45720133 84.26110851 61.03412324 84.28344727 62.61096191 C84.31307026 64.8085447 84.32331211 67.00536665 84.328125 69.203125 C84.3374707 70.44739258 84.34681641 71.69166016 84.35644531 72.97363281 C84 76 84 76 82.73034668 77.68478394 C80.56608158 79.32981626 79.63802574 79.28920572 76.95800781 79.06982422 C75.71830231 78.98657394 75.71830231 78.98657394 74.45355225 78.90164185 C73.54591125 78.82839691 72.63827026 78.75515198 71.703125 78.6796875 C62.53186675 78.09888659 53.42327768 77.92843278 44.234375 78.0078125 C42.339151 78.0199881 42.339151 78.0199881 40.40563965 78.03240967 C35.17784805 78.06788344 29.95020985 78.1100683 24.72265625 78.17138672 C20.85162217 78.21602536 16.98061225 78.23702797 13.109375 78.2578125 C11.32536285 78.28574387 11.32536285 78.28574387 9.50531006 78.3142395 C7.87024506 78.31942093 7.87024506 78.31942093 6.20214844 78.32470703 C4.76279938 78.33922409 4.76279938 78.33922409 3.29437256 78.35403442 C2.15865814 78.17878738 2.15865814 78.17878738 1 78 C-1.11766624 74.82350064 -1.24885987 74.11349575 -1.23046875 70.48828125 C-1.22917969 69.52510986 -1.22789062 68.56193848 -1.2265625 67.56958008 C-1.21367187 66.49474365 -1.20078125 65.41990723 -1.1875 64.3125 C-1.18105469 63.18158936 -1.17460937 62.05067871 -1.16796875 60.88549805 C-1.00069226 40.5888483 -0.4546929 20.29067045 0 0 Z " fill="#4C0F0C" transform="translate(472,225)"/>
<path d="M0 0 C8.45159814 -0.02350035 16.90318192 -0.04110214 25.35480499 -0.05181217 C29.28212158 -0.05696064 33.20941531 -0.06390567 37.13671875 -0.07543945 C53.56520732 -0.12238185 69.94755695 -0.05605073 86.36132812 0.74389648 C96.40041688 1.22706882 106.45208983 1.36454744 116.5 1.5625 C118.60677558 1.60589009 120.71354645 1.64950917 122.8203125 1.69335938 C127.88015843 1.79819282 132.94005639 1.8999973 138 2 C138 2.33 138 2.66 138 3 C92.79 3 47.58 3 1 3 C1 5.64 1 8.28 1 11 C0.67 11 0.34 11 0 11 C0 7.37 0 3.74 0 0 Z " fill="#6F7576" transform="translate(326,395)"/>
<path d="M0 0 C0.33 0 0.66 0 1 0 C1 7.26 1 14.52 1 22 C37.3 22 73.6 22 111 22 C111.495 22.99 111.495 22.99 112 24 C74.71 24 37.42 24 -1 24 C-0.67 16.08 -0.34 8.16 0 0 Z " fill="#0E1418" transform="translate(551,563)"/>
<path d="M0 0 C0.33 0 0.66 0 1 0 C1.57831229 18.95510799 2.11107346 37.91589618 2.23217773 56.88024902 C2.24403757 58.45720133 2.26110851 60.03412324 2.28344727 61.61096191 C2.31307026 63.8085447 2.32331211 66.00536665 2.328125 68.203125 C2.3374707 69.44739258 2.34681641 70.69166016 2.35644531 71.97363281 C2 75 2 75 0.79858398 76.92016602 C-1.65515594 78.39334257 -3.26106965 78.2550765 -6.10546875 78.07421875 C-7.08837891 78.01943359 -8.07128906 77.96464844 -9.08398438 77.90820312 C-10.62022461 77.79895508 -10.62022461 77.79895508 -12.1875 77.6875 C-13.22326172 77.62626953 -14.25902344 77.56503906 -15.32617188 77.50195312 C-17.88531957 77.34864013 -20.44267045 77.1807893 -23 77 C-23.33 76.34 -23.66 75.68 -24 75 C-11.625 74.505 -11.625 74.505 1 74 C0.835 70.32875 0.67 66.6575 0.5 62.875 C-0.33621628 41.9290255 -0.08802477 20.95802133 0 0 Z " fill="#390D0B" transform="translate(554,226)"/>
<path d="M0 0 C0.66 0 1.32 0 2 0 C2 15.18 2 30.36 2 46 C0.68 45.67 -0.64 45.34 -2 45 C-1.88269497 38.75188831 -1.75785931 32.50396027 -1.62768555 26.25610352 C-1.58426577 24.12885993 -1.54259607 22.0015799 -1.50268555 19.87426758 C-1.44515293 16.82361849 -1.38141033 13.77315411 -1.31640625 10.72265625 C-1.29969376 9.76537125 -1.28298126 8.80808624 -1.26576233 7.8217926 C-1.24581711 6.94095505 -1.22587189 6.06011749 -1.20532227 5.15258789 C-1.18977798 4.37315323 -1.1742337 3.59371857 -1.15821838 2.79066467 C-1 1 -1 1 0 0 Z " fill="#D1D3D3" transform="translate(228,599)"/>
<path d="M0 0 C35.64 0.33 71.28 0.66 108 1 C108 1.33 108 1.66 108 2 C54.54 2.495 54.54 2.495 0 3 C0 2.01 0 1.02 0 0 Z " fill="#F5F7F7" transform="translate(117,521)"/>
<path d="M0 0 C6.93 0 13.86 0 21 0 C21 0.99 21 1.98 21 3 C14.07 3 7.14 3 0 3 C0 2.01 0 1.02 0 0 Z " fill="#272A2A" transform="translate(158,292)"/>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

+1
View File
@@ -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

+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
<circle cx="250" cy="250" r="250" fill="#fff"/>
<path d="m335 150h40v200h-40zm-130 0a100 100 0 1 0 0 200 100 100 0 1 0 0-200zm0 40a60 60 0 1 1 0 120 60 60 0 1 1 0-120z"/>
</svg>

After

Width:  |  Height:  |  Size: 293 B

+8
View File
@@ -0,0 +1,8 @@
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<circle cx="128" cy="128" r="120" fill="black"/>
<polygon
points="128,70 178,170 78,170"
fill="white"
/>
</svg>

After

Width:  |  Height:  |  Size: 216 B

+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 440 440">
<g id="Layer_6" data-name="Layer 6">
<rect x="12" y="12" width="416" height="416" rx="103" ry="103" fill="#3a78b1" stroke-width="0"/>
</g>
<g id="Layer_8" data-name="Layer 8">
<path d="M83.6,373.6v-178.6c0-63,52.4-143.7,142.7-143.7s144.1,69.7,144.1,141.1c0,122.6-116.6,143.1-116.6,143.1,0,0,7.2-11.5,7.2-29.5s-8-28-8-28c0,0,60-16,60-87s-53-83-86-83c-57,0-88.3,48.5-88.3,84.3v181.9c0,25.9-17.5,23.9-17.5,23.9h-19.2s-18.4,0-18.4-24.4Z" fill="#fff" stroke-width="0"/>
</g>
<g id="Layer_9" data-name="Layer 9">
<circle cx="204.9" cy="306.6" r="48" fill="#fff" stroke-width="0"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 727 B

+22
View File
@@ -0,0 +1,22 @@
# ShellCheck configuration for Coder Registry
# https://www.shellcheck.net/wiki/
# Set default shell dialect to bash (most scripts use bash)
shell=bash
# Disable checks that conflict with Terraform templating syntax
# Many scripts use Terraform's templatefile() function with $${VAR} escape syntax
disable=SC2154 # Variable is referenced but not assigned (injected by Terraform)
disable=SC2034 # Variable appears unused (used via $${VAR} syntax)
disable=SC1083 # Literal braces (Terraform's $${VAR} escape syntax)
disable=SC2193 # Comparison arguments never equal (Terraform interpolation)
disable=SC2125 # Brace expansion/globs in assignments (Terraform syntax)
disable=SC2157 # Argument to -n/-z is always true/false (Terraform $${VAR} syntax)
disable=SC2066 # Loop will only run once (Terraform $${VAR} array syntax)
# Disable checks that conflict with intentional patterns
disable=SC2076 # Quoted regex in =~ (intentional literal string match, not regex, for array membership checks)
# Enable all optional checks for thorough analysis
enable=all
+1 -1
View File
@@ -39,7 +39,7 @@ module "cursor" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/cursor/coder"
version = "1.0.19"
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
}
```
+252 -12
View File
@@ -1,17 +1,19 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "registry",
"devDependencies": {
"@types/bun": "^1.2.21",
"bun-types": "^1.2.21",
"dedent": "^1.6.0",
"@types/bun": "^1.3.4",
"bun-types": "^1.3.4",
"dedent": "^1.7.0",
"gray-matter": "^4.0.3",
"marked": "^16.2.0",
"prettier": "^3.6.2",
"marked": "^16.4.2",
"prettier": "^3.7.4",
"prettier-plugin-sh": "^0.18.0",
"prettier-plugin-terraform-formatter": "^1.2.1",
"shellcheck": "^4.1.0",
},
"peerDependencies": {
"typescript": "^5.8.3",
@@ -19,54 +21,292 @@
},
},
"packages": {
"@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="],
"@felipecrs/decompress-tarxz": ["@felipecrs/decompress-tarxz@5.0.4", "", { "dependencies": { "@xhmikosr/decompress-tar": "^8.1.0", "file-type": "^20.5.0", "is-stream": "^2.0.1", "xz-decompress": "^0.2.3" } }, "sha512-a+nAnDsiUA84Sy/a+FKYJtjOjFvNtW8Jcbi3NwE8kJKPpYAxINFLYsC9mev9/wngiNEBA3jfHn0qNFwICeZNJw=="],
"@reteps/dockerfmt": ["@reteps/dockerfmt@0.3.6", "", {}, "sha512-Tb5wIMvBf/nLejTQ61krK644/CEMB/cpiaIFXqGApfGqO3GwcR3qnI0DbmkFVCl2OyEp8LnLX3EkucoL0+tbFg=="],
"@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
"@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="],
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
"@types/node": ["@types/node@24.0.14", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw=="],
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
"@xhmikosr/decompress-tar": ["@xhmikosr/decompress-tar@8.1.0", "", { "dependencies": { "file-type": "^20.5.0", "is-stream": "^2.0.1", "tar-stream": "^3.1.7" } }, "sha512-m0q8x6lwxenh1CrsTby0Jrjq4vzW/QU1OLhTHMQLEdHpmjR1lgahGz++seZI0bXF3XcZw3U3xHfqZSz+JPP2Gg=="],
"@xhmikosr/decompress-unzip": ["@xhmikosr/decompress-unzip@7.1.0", "", { "dependencies": { "file-type": "^20.5.0", "get-stream": "^6.0.1", "yauzl": "^3.1.2" } }, "sha512-oqTYAcObqTlg8owulxFTqiaJkfv2SHsxxxz9Wg4krJAHVzGWlZsU8tAB30R6ow+aHrfv4Kub6WQ8u04NWVPUpA=="],
"argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"b4a": ["b4a@1.7.3", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q=="],
"dedent": ["dedent@1.6.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA=="],
"bare-events": ["bare-events@2.8.0", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-AOhh6Bg5QmFIXdViHbMc2tLDsBIRxdkIaIddPslJF9Z5De3APBScuqGP2uThXnIpqFrgoxMNC6km7uXNIMLHXA=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"bl": ["bl@1.2.3", "", { "dependencies": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" } }, "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww=="],
"boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="],
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
"buffer-alloc": ["buffer-alloc@1.2.0", "", { "dependencies": { "buffer-alloc-unsafe": "^1.1.0", "buffer-fill": "^1.0.0" } }, "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow=="],
"buffer-alloc-unsafe": ["buffer-alloc-unsafe@1.1.0", "", {}, "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="],
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
"buffer-fill": ["buffer-fill@1.0.0", "", {}, "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ=="],
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"decompress": ["decompress@4.2.1", "", { "dependencies": { "decompress-tar": "^4.0.0", "decompress-tarbz2": "^4.0.0", "decompress-targz": "^4.0.0", "decompress-unzip": "^4.0.1", "graceful-fs": "^4.1.10", "make-dir": "^1.0.0", "pify": "^2.3.0", "strip-dirs": "^2.0.0" } }, "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ=="],
"decompress-tar": ["decompress-tar@4.1.1", "", { "dependencies": { "file-type": "^5.2.0", "is-stream": "^1.1.0", "tar-stream": "^1.5.2" } }, "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ=="],
"decompress-tarbz2": ["decompress-tarbz2@4.1.1", "", { "dependencies": { "decompress-tar": "^4.1.0", "file-type": "^6.1.0", "is-stream": "^1.1.0", "seek-bzip": "^1.0.5", "unbzip2-stream": "^1.0.9" } }, "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A=="],
"decompress-targz": ["decompress-targz@4.1.1", "", { "dependencies": { "decompress-tar": "^4.1.1", "file-type": "^5.2.0", "is-stream": "^1.1.0" } }, "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w=="],
"decompress-unzip": ["decompress-unzip@4.0.1", "", { "dependencies": { "file-type": "^3.8.0", "get-stream": "^2.2.0", "pify": "^2.3.0", "yauzl": "^2.4.2" } }, "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw=="],
"dedent": ["dedent@1.7.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ=="],
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
"detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
"envalid": ["envalid@8.1.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-OT6+qVhKVyCidaGoXflb2iK1tC8pd0OV2Q+v9n33wNhUJ+lus+rJobUj4vJaQBPxPZ0vYrPGuxdrenyCAIJcow=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
"events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="],
"extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="],
"fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="],
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
"fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
"file-type": ["file-type@20.5.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.6", "strtok3": "^10.2.0", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg=="],
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
"global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="],
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="],
"has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="],
"is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="],
"is-natural-number": ["is-natural-number@4.0.1", "", {}, "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ=="],
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
"is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
"js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="],
"json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="],
"kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
"marked": ["marked@16.2.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-LbbTuye+0dWRz2TS9KJ7wsnD4KAtpj0MVkWc90XvBa6AslXsT0hTBVH5k32pcSyHH1fst9XEFJunXHktVy0zlg=="],
"make-dir": ["make-dir@1.3.0", "", { "dependencies": { "pify": "^3.0.0" } }, "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ=="],
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
"marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="],
"matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
"pinkie": ["pinkie@2.0.4", "", {}, "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg=="],
"pinkie-promise": ["pinkie-promise@2.0.1", "", { "dependencies": { "pinkie": "^2.0.0" } }, "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw=="],
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
"prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
"prettier-plugin-sh": ["prettier-plugin-sh@0.18.0", "", { "dependencies": { "@reteps/dockerfmt": "^0.3.6", "sh-syntax": "^0.5.8" }, "peerDependencies": { "prettier": "^3.6.0" } }, "sha512-cW1XL27FOJQ/qGHOW6IHwdCiNWQsAgK+feA8V6+xUTaH0cD3Mh+tFAtBvEEWvuY6hTDzRV943Fzeii+qMOh7nQ=="],
"prettier-plugin-terraform-formatter": ["prettier-plugin-terraform-formatter@1.2.1", "", { "peerDependencies": { "prettier": ">= 1.16.0" }, "optionalPeers": ["prettier"] }, "sha512-rdzV61Bs/Ecnn7uAS/vL5usTX8xUWM+nQejNLZxt3I1kJH5WSeLEmq7LYu1wCoEQF+y7Uv1xGvPRfl3lIe6+tA=="],
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
"readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
"roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
"safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="],
"seek-bzip": ["seek-bzip@1.0.6", "", { "dependencies": { "commander": "^2.8.1" }, "bin": { "seek-bunzip": "bin/seek-bunzip", "seek-table": "bin/seek-bzip-table" } }, "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ=="],
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="],
"serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="],
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
"sh-syntax": ["sh-syntax@0.5.8", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-JfVoxf4FxQI5qpsPbkHhZo+n6N9YMJobyl4oGEUBb/31oQYlgTjkXQD8PBiafS2UbWoxrTO0Z5PJUBXEPAG1Zw=="],
"shellcheck": ["shellcheck@4.1.0", "", { "dependencies": { "@felipecrs/decompress-tarxz": "5.0.4", "@xhmikosr/decompress-unzip": "7.1.0", "decompress": "4.2.1", "envalid": "8.1.0", "global-agent": "3.0.0" }, "bin": { "shellcheck": "bin/shellcheck.js" } }, "sha512-8143z6YGO4+Puwp9Ghn/g7+QxllSKlXaZSm3HXfvQXUfRXhM5P8TPORRHBBlyobl9BnniVne+d1Ff6RgNiccsQ=="],
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
"streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="],
"string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"strip-bom-string": ["strip-bom-string@1.0.0", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="],
"strip-dirs": ["strip-dirs@2.1.0", "", { "dependencies": { "is-natural-number": "^4.0.1" } }, "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g=="],
"strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
"tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
"text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="],
"through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="],
"to-buffer": ["to-buffer@1.2.2", "", { "dependencies": { "isarray": "^2.0.5", "safe-buffer": "^5.2.1", "typed-array-buffer": "^1.0.3" } }, "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw=="],
"token-types": ["token-types@6.1.1", "", { "dependencies": { "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
"unbzip2-stream": ["unbzip2-stream@1.4.3", "", { "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" } }, "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg=="],
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
"xz-decompress": ["xz-decompress@0.2.3", "", {}, "sha512-O8v6HG8T0PrKBcpyWA13GkSYWFvncwzuzcLx5A7++l3HsE3atmoetXjIxrZ/JV/nbvSZ7WS4+3XvREZuVn+rEA=="],
"yauzl": ["yauzl@3.2.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "pend": "~1.2.0" } }, "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w=="],
"decompress-tar/file-type": ["file-type@5.2.0", "", {}, "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ=="],
"decompress-tar/is-stream": ["is-stream@1.1.0", "", {}, "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ=="],
"decompress-tar/tar-stream": ["tar-stream@1.6.2", "", { "dependencies": { "bl": "^1.0.0", "buffer-alloc": "^1.2.0", "end-of-stream": "^1.0.0", "fs-constants": "^1.0.0", "readable-stream": "^2.3.0", "to-buffer": "^1.1.1", "xtend": "^4.0.0" } }, "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A=="],
"decompress-tarbz2/file-type": ["file-type@6.2.0", "", {}, "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg=="],
"decompress-tarbz2/is-stream": ["is-stream@1.1.0", "", {}, "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ=="],
"decompress-targz/file-type": ["file-type@5.2.0", "", {}, "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ=="],
"decompress-targz/is-stream": ["is-stream@1.1.0", "", {}, "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ=="],
"decompress-unzip/file-type": ["file-type@3.9.0", "", {}, "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA=="],
"decompress-unzip/get-stream": ["get-stream@2.3.1", "", { "dependencies": { "object-assign": "^4.0.1", "pinkie-promise": "^2.0.0" } }, "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA=="],
"decompress-unzip/yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
"make-dir/pify": ["pify@3.0.0", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="],
"roarr/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
"to-buffer/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
"to-buffer/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
}
}
@@ -6,7 +6,7 @@ Run the [Goose](https://block.github.io/goose/) agent in your workspace to gener
module "goose" {
source = "registry.coder.com/coder/goose/coder"
version = "1.0.31"
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
folder = "/home/coder"
install_goose = true
goose_version = "v1.0.16"
@@ -40,7 +40,7 @@ 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.example.id
agent_id = coder_agent.main.id
}
variable "anthropic_api_key" {
@@ -82,7 +82,7 @@ module "goose" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/goose/coder"
version = "1.0.31"
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
folder = "/home/coder"
install_goose = true
goose_version = "v1.0.16"
@@ -110,7 +110,7 @@ Run Goose as a standalone app in your workspace. This will install Goose and run
module "goose" {
source = "registry.coder.com/coder/goose/coder"
version = "1.0.31"
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
folder = "/home/coder"
install_goose = true
goose_version = "v1.0.16"
+3 -3
View File
@@ -31,7 +31,7 @@ module "MODULE_NAME" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/NAMESPACE/MODULE_NAME/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
extensions = [
"dracula-theme.theme-dracula"
]
@@ -49,7 +49,7 @@ module "MODULE_NAME" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/NAMESPACE/MODULE_NAME/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
extensions = ["dracula-theme.theme-dracula"]
settings = {
"workbench.colorTheme" = "Dracula"
@@ -65,7 +65,7 @@ Run code-server in the background, don't fetch it from GitHub:
module "MODULE_NAME" {
source = "registry.coder.com/NAMESPACE/MODULE_NAME/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
offline = true
}
```
+4 -4
View File
@@ -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
)
+10 -10
View File
@@ -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=
+10 -7
View File
@@ -4,18 +4,21 @@
"fmt": "bun x prettier --write . && terraform fmt -recursive -diff",
"fmt:ci": "bun x prettier --check . && terraform fmt -check -recursive -diff",
"terraform-validate": "./scripts/terraform_validate.sh",
"test": "./scripts/terraform_test_all.sh",
"tftest": "./scripts/terraform_test_all.sh",
"tstest": "./scripts/ts_test_auto.sh",
"shellcheck": "./scripts/shellcheck_validate.sh",
"update-version": "./update-version.sh"
},
"devDependencies": {
"@types/bun": "^1.2.21",
"bun-types": "^1.2.21",
"dedent": "^1.6.0",
"@types/bun": "^1.3.4",
"bun-types": "^1.3.4",
"dedent": "^1.7.0",
"gray-matter": "^4.0.3",
"marked": "^16.2.0",
"prettier": "^3.6.2",
"marked": "^16.4.2",
"prettier": "^3.7.4",
"prettier-plugin-sh": "^0.18.0",
"prettier-plugin-terraform-formatter": "^1.2.1"
"prettier-plugin-terraform-formatter": "^1.2.1",
"shellcheck": "^4.1.0"
},
"peerDependencies": {
"typescript": "^5.8.3"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 KiB

After

Width:  |  Height:  |  Size: 451 KiB

+2 -2
View File
@@ -17,7 +17,7 @@ It can be served on a Coder subdomain for easy access, or on `localhost` if you
module "pgadmin" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/AJ0070/pgadmin/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
version = "1.0.1"
agent_id = coder_agent.main.id
}
```
@@ -14,8 +14,8 @@ Launches RustDesk within your workspace with a virtual display to provide remote
module "rustdesk" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/BenraouaneSoufiane/rustdesk/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
version = "1.0.1"
agent_id = coder_agent.main.id
}
```
@@ -41,8 +41,8 @@ module "rustdesk" {
module "rustdesk" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/BenraouaneSoufiane/rustdesk/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
version = "1.0.1"
agent_id = coder_agent.main.id
rustdesk_password = "mycustompass"
xvfb_resolution = "1920x1080x24"
rustdesk_version = "1.4.1"
Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

+7
View File
@@ -0,0 +1,7 @@
---
display_name: "Excellencedev"
bio: "Love to contribute"
avatar: "./.images/avatar.png"
support_email: "ademiluyisuccessandexcellence@gmail.com"
status: "community"
---
@@ -0,0 +1,32 @@
---
display_name: Hetzner Cloud Server
description: Provision Hetzner Cloud servers as Coder workspaces
icon: ../../../../.icons/hetzner.svg
tags: [vm, linux, hetzner]
---
# Remote Development on Hetzner Cloud (Linux)
Provision Hetzner Cloud servers as [Coder workspaces](https://coder.com/docs/workspaces) with this example template.
> [!WARNING]
> **Workspace Storage Persistence:** When a workspace is stopped, the Hetzner Cloud server instance is stopped but your home volume and stored data persist. This means your files and data remain intact when you resume the workspace.
> [!IMPORTANT]
> **Volume Management & Costs:** Hetzner Cloud volumes persist even when workspaces are stopped and will continue to incur storage costs (€0.0476/GB/month). Volumes are only automatically deleted when the workspace is completely deleted. Monitor your volumes in the [Hetzner Cloud Console](https://console.hetzner.cloud/) to manage costs effectively.
## Prerequisites
To deploy workspaces as Hetzner Cloud servers, you'll need:
- Hetzner Cloud [API token](https://console.hetzner.cloud/projects) (create under Security > API Tokens)
### Authentication
This template assumes that the Coder Provisioner is run in an environment that is authenticated with Hetzner Cloud.
Obtain a Hetzner Cloud API token from your [Hetzner Cloud Console](https://console.hetzner.cloud/projects) and provide it as the `hcloud_token` variable when creating a workspace.
For more authentication options, see the [Terraform provider documentation](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs#authentication).
> [!NOTE]
> This template is designed to be a starting point. Edit the Terraform to extend the template to support your use case.
@@ -0,0 +1,62 @@
#cloud-config
users:
- name: ${username}
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
groups: sudo
shell: /bin/bash
packages:
- git
%{ if home_volume_label != "" ~}
fs_setup:
- device: /dev/disk/by-id/scsi-0HC_Volume_${volume_id}
filesystem: ext4
label: ${home_volume_label}
overwrite: false # This prevents reformatting the disk on every boot
mounts:
- [
"/dev/disk/by-id/scsi-0HC_Volume_${volume_id}",
"/home/${username}",
ext4,
"defaults,uid=1000,gid=1000",
]
%{ endif ~}
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=-900
SyslogIdentifier=coder-agent
[Install]
WantedBy=multi-user.target
runcmd:
%{ if home_volume_label != "" ~}
- |
until [ -e /dev/disk/by-id/scsi-0HC_Volume_${volume_id} ]; do
echo "Waiting for volume device..."
sleep 2
done
%{ endif ~}
- mount -a
- chown ${username}:${username} /home/${username}
- systemctl enable coder-agent
- systemctl start coder-agent
@@ -0,0 +1,27 @@
{
"type_meta": {
"cx22": { "cores": 2, "memory_gb": 4, "disk_gb": 40 },
"cx32": { "cores": 4, "memory_gb": 8, "disk_gb": 80 },
"cx42": { "cores": 8, "memory_gb": 16, "disk_gb": 160 },
"cx52": { "cores": 16, "memory_gb": 32, "disk_gb": 320 },
"cpx11": { "cores": 2, "memory_gb": 2, "disk_gb": 40 },
"cpx21": { "cores": 3, "memory_gb": 4, "disk_gb": 80 },
"cpx31": { "cores": 4, "memory_gb": 8, "disk_gb": 160 },
"cpx41": { "cores": 8, "memory_gb": 16, "disk_gb": 240 },
"cpx51": { "cores": 16, "memory_gb": 32, "disk_gb": 360 },
"ccx13": { "cores": 2, "memory_gb": 8, "disk_gb": 80 },
"ccx23": { "cores": 4, "memory_gb": 16, "disk_gb": 160 },
"ccx33": { "cores": 8, "memory_gb": 32, "disk_gb": 240 },
"ccx43": { "cores": 16, "memory_gb": 64, "disk_gb": 360 },
"ccx53": { "cores": 32, "memory_gb": 128, "disk_gb": 600 },
"ccx63": { "cores": 48, "memory_gb": 192, "disk_gb": 960 }
},
"availability": {
"fsn1": ["cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"],
"ash": ["cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"],
"hel1": ["cx22", "cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"],
"hil": ["cpx11", "cpx21", "cpx31", "cpx41", "ccx13", "ccx23", "ccx33"],
"nbg1": ["cx22", "cx32", "cx42", "cx52", "cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"],
"sin": ["cpx11", "cpx21", "cpx31", "cpx41", "cpx51", "ccx13", "ccx23", "ccx33"]
}
}
@@ -0,0 +1,183 @@
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
}
coder = {
source = "coder/coder"
}
}
}
variable "hcloud_token" {
sensitive = true
}
provider "hcloud" {
token = var.hcloud_token
}
# Available locations: https://docs.hetzner.com/cloud/general/locations/
data "coder_parameter" "hcloud_location" {
name = "hcloud_location"
display_name = "Hetzner Location"
description = "Select the Hetzner Cloud location for your workspace."
type = "string"
default = "fsn1"
option {
name = "DE Falkenstein"
value = "fsn1"
}
option {
name = "US Ashburn, VA"
value = "ash"
}
option {
name = "US Hillsboro, OR"
value = "hil"
}
option {
name = "SG Singapore"
value = "sin"
}
option {
name = "DE Nuremberg"
value = "nbg1"
}
option {
name = "FI Helsinki"
value = "hel1"
}
}
# Available server types: https://docs.hetzner.com/cloud/servers/overview/
data "coder_parameter" "hcloud_server_type" {
name = "hcloud_server_type"
display_name = "Hetzner Server Type"
description = "Select the Hetzner Cloud server type for your workspace."
type = "string"
dynamic "option" {
for_each = local.hcloud_server_type_options_for_selected_location
content {
name = option.value.name
value = option.value.value
}
}
}
resource "hcloud_server" "dev" {
count = data.coder_workspace.me.start_count
name = "coder-${data.coder_workspace.me.name}-dev"
image = "ubuntu-24.04"
server_type = data.coder_parameter.hcloud_server_type.value
location = data.coder_parameter.hcloud_location.value
public_net {
ipv4_enabled = true
ipv6_enabled = true
}
user_data = templatefile("cloud-config.yaml.tftpl", {
username = lower(data.coder_workspace_owner.me.name)
home_volume_label = "coder-${data.coder_workspace.me.id}-home"
volume_id = hcloud_volume.home_volume.id
init_script = base64encode(coder_agent.main.init_script)
coder_agent_token = coder_agent.main.token
})
labels = {
"coder_workspace_name" = data.coder_workspace.me.name,
"coder_workspace_owner" = data.coder_workspace_owner.me.name,
}
}
resource "hcloud_volume" "home_volume" {
name = "coder-${data.coder_workspace.me.id}-home"
size = data.coder_parameter.home_volume_size.value
location = data.coder_parameter.hcloud_location.value
labels = {
"coder_workspace_name" = data.coder_workspace.me.name,
"coder_workspace_owner" = data.coder_workspace_owner.me.name,
}
}
resource "hcloud_volume_attachment" "home_volume_attachment" {
count = data.coder_workspace.me.start_count
volume_id = hcloud_volume.home_volume.id
server_id = hcloud_server.dev[count.index].id
automount = false
}
locals {
username = lower(data.coder_workspace_owner.me.name)
# Data source: local JSON file under the module directory
# Check API for latest server types & availability: https://docs.hetzner.cloud/reference/cloud#server-types
hcloud_server_types_data = jsondecode(file("${path.module}/hetzner_server_types.json"))
hcloud_server_type_meta = local.hcloud_server_types_data.type_meta
hcloud_server_types_by_location = local.hcloud_server_types_data.availability
hcloud_server_type_options_for_selected_location = [
for type_name in lookup(local.hcloud_server_types_by_location, data.coder_parameter.hcloud_location.value, []) : {
name = format("%s (%d vCPU, %dGB RAM, %dGB)", upper(type_name), local.hcloud_server_type_meta[type_name].cores, local.hcloud_server_type_meta[type_name].memory_gb, local.hcloud_server_type_meta[type_name].disk_gb)
value = type_name
}
]
}
data "coder_provisioner" "me" {}
provider "coder" {}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
data "coder_parameter" "home_volume_size" {
name = "home_volume_size"
display_name = "Home volume size"
description = "How large would you like your home volume to be (in GB)?"
type = "number"
default = "20"
mutable = false
validation {
min = 1
max = 100 # Adjust the max size as needed
}
}
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/${local.username}"
}
}
module "code-server" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/code-server/coder"
# This ensures that the latest non-breaking version of the module gets downloaded, you can also pin the module version to prevent breaking changes in production.
version = "~> 1.0"
agent_id = coder_agent.main.id
order = 1
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

+3 -3
View File
@@ -15,7 +15,7 @@ up a default or custom tmux configuration with session save/restore capabilities
```tf
module "tmux" {
source = "registry.coder.com/anomaly/tmux/coder"
version = "1.0.1"
version = "1.0.3"
agent_id = coder_agent.example.id
}
```
@@ -39,7 +39,7 @@ module "tmux" {
```tf
module "tmux" {
source = "registry.coder.com/anomaly/tmux/coder"
version = "1.0.1"
version = "1.0.3"
agent_id = coder_agent.example.id
tmux_config = "" # Optional: custom tmux.conf content
save_interval = 1 # Optional: save interval in minutes
@@ -78,7 +78,7 @@ This module can provision multiple tmux sessions, each as a separate app in the
```tf
module "tmux" {
source = "registry.coder.com/anomaly/tmux/coder"
version = "1.0.1"
version = "1.0.3"
agent_id = var.agent_id
sessions = ["default", "dev", "anomaly"]
tmux_config = <<-EOT
+1 -1
View File
@@ -144,7 +144,7 @@ main() {
printf "$${BOLD}✅ tmux setup complete! \n\n"
printf "$${BOLD} Attempting to restore sessions\n"
tmux new-session -d \; source-file ~/.tmux.conf \; run-shell '~/.tmux/plugins/tmux-resurrect/scripts/restore.sh'
tmux new-session -d \; source-file ~/.tmux.conf \; run-shell "$HOME/.tmux/plugins/tmux-resurrect/scripts/restore.sh"
printf "$${BOLD} Sessions restored: -> %s\n" "$(tmux ls)"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 KiB

@@ -0,0 +1,163 @@
---
display_name: Archive
description: Create automated and user-invocable scripts that archive and extract selected files/directories with optional compression (gzip or zstd).
icon: ../../../../.icons/folder.svg
verified: false
tags: [backup, archive, tar, helper]
---
# Archive
This module installs small, robust scripts in your workspace to create and extract tar archives from a list of files and directories. It supports optional compression (gzip or zstd). The create command prints only the resulting archive path to stdout; operational logs go to stderr. An optional stop hook can also create an archive automatically when the workspace stops, and an optional start hook can wait for an archive on-disk and extract it on start.
```tf
module "archive" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/archive/coder"
version = "0.0.3"
agent_id = coder_agent.example.id
paths = ["./projects", "./code"]
}
```
## Features
- Installs two commands into the workspace `$PATH`: `coder-archive-create` and `coder-archive-extract`.
- Creates a single `.tar`, `.tar.gz`, or `.tar.zst` containing selected paths (depends on `tar`).
- Optional compression: `gzip`, `zstd` (depends on `gzip` or `zstd`).
- Stores defaults so commands can be run without arguments (supports overriding via CLI flags).
- Logs and status messages go to stderr, the create command prints only the final archive path to stdout.
- Optional:
- `create_on_stop` to create an archive automatically when the workspace stops.
- `extract_on_start` to wait for an archive to appear and extract it on start.
> [!WARNING]
> The `create_on_stop` feature uses the `coder_script` `run_on_stop` which may not work as expected on certain templates without additional provider configuration. The agent may be terminated before the script completes. See [coder/coder#6174](https://github.com/coder/coder/issues/6174) for provider-specific workarounds and [coder/coder#6175](https://github.com/coder/coder/issues/6175) for tracking a fix.
## Usage
Basic example:
```tf
module "archive" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/archive/coder"
version = "0.0.3"
agent_id = coder_agent.example.id
# Paths to include in the archive (files or directories).
directory = "~"
paths = [
"./projects",
"./code",
]
}
```
Customize compression and output:
```tf
module "archive" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/archive/coder"
version = "0.0.3"
agent_id = coder_agent.example.id
directory = "/"
paths = ["/etc", "/home"]
compression = "zstd" # "gzip" | "zstd" | "none"
output_dir = "/tmp/backup" # defaults to /tmp
archive_name = "my-backup" # base name (extension is inferred from compression)
}
```
Enable auto-archive on stop:
```tf
module "archive" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/archive/coder"
version = "0.0.3"
agent_id = coder_agent.example.id
# Creates /tmp/coder-archive.tar.gz of the users home directory (defaults).
create_on_stop = true
}
```
Extract on start:
```tf
module "archive" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/archive/coder"
version = "0.0.3"
agent_id = coder_agent.example.id
# Where to look for the archive file to extract:
output_dir = "/tmp"
archive_name = "my-archive"
compression = "gzip"
# Waits up to 5 minutes for /tmp/my-archive.tar.gz to be present, note that
# using a long timeout will delay every workspace start by this much until the
# archive is present.
extract_on_start = true
extract_wait_timeout_seconds = 300
}
```
## Command usage
The installer writes the following files:
- `$CODER_SCRIPT_DATA_DIR/archive-lib.sh`
- `$CODER_SCRIPT_BIN_DIR/coder-archive-create`
- `$CODER_SCRIPT_BIN_DIR/coder-archive-extract`
Create usage:
```console
coder-archive-create [OPTIONS] [PATHS...]
-c, --compression <gzip|zstd|none> Compression algorithm (default from module)
-C, --directory <DIRECTORY> Change to directory for archiving (default from module)
-f, --file <ARCHIVE> Output archive file (default from module)
-h, --help Show help
```
Extract usage:
```console
coder-archive-extract [OPTIONS]
-c, --compression <gzip|zstd|none> Compression algorithm (default from module)
-C, --directory <DIRECTORY> Extract into directory (default from module)
-f, --file <ARCHIVE> Archive file to extract (default from module)
-h, --help Show help
```
Examples:
- Use Terraform defaults:
```
coder-archive-create
```
- Override compression and output file at runtime:
```
coder-archive-create --compression zstd --file /tmp/backups/archive.tar.zst
```
- Add extra paths on the fly (in addition to the Terraform defaults):
```
coder-archive-create /etc/hosts
```
- Extract an archive into a directory:
```
coder-archive-extract --file /tmp/backups/archive.tar.gz --directory /tmp/restore
```
@@ -0,0 +1,33 @@
mock_provider "coder" {}
run "apply_defaults" {
command = apply
variables {
agent_id = "agent-123"
paths = ["~/project", "/etc/hosts"]
}
assert {
condition = output.archive_path == "/tmp/coder-archive.tar.gz"
error_message = "archive_path should be empty when archive_name is not set"
}
}
run "apply_with_name" {
command = apply
variables {
agent_id = "agent-123"
paths = ["/etc/hosts"]
archive_name = "nightly"
output_dir = "/tmp/backups"
compression = "zstd"
create_archive_on_stop = true
}
assert {
condition = output.archive_path == "/tmp/backups/nightly.tar.zst"
error_message = "archive_path should be computed from archive_name + output_dir + extension"
}
}
@@ -0,0 +1,348 @@
import { describe, expect, it, beforeAll } from "bun:test";
import {
execContainer,
findResourceInstance,
runContainer,
runTerraformApply,
runTerraformInit,
testRequiredVariables,
type TerraformState,
} from "~test";
const USE_XTRACE =
process.env.ARCHIVE_TEST_XTRACE === "1" || process.env.XTRACE === "1";
const IMAGE = "alpine";
const BIN_DIR = "/tmp/coder-script-data/bin";
const DATA_DIR = "/tmp/coder-script-data";
type ExecResult = {
exitCode: number;
stdout: string;
stderr: string;
};
const ensureRunOk = (label: string, res: ExecResult) => {
if (res.exitCode !== 0) {
console.error(
`[${label}] non-zero exit code: ${res.exitCode}\n--- stdout ---\n${res.stdout.trim()}\n--- stderr ---\n${res.stderr.trim()}\n--------------`,
);
}
expect(res.exitCode).toBe(0);
};
const sh = async (id: string, cmd: string): Promise<ExecResult> => {
const res = await execContainer(id, ["sh", "-c", cmd]);
return res;
};
const bashRun = async (id: string, cmd: string): Promise<ExecResult> => {
const injected = USE_XTRACE ? `/bin/bash -x ${cmd}` : cmd;
return sh(id, injected);
};
const prepareContainer = async (image = IMAGE) => {
const id = await runContainer(image);
// Prepare script dirs and deps.
ensureRunOk(
"mkdirs",
await sh(id, `mkdir -p ${BIN_DIR} ${DATA_DIR} /tmp/backup`),
);
// Install tools used by tests.
ensureRunOk(
"apk add",
await sh(id, "apk add --no-cache bash tar gzip zstd coreutils"),
);
return id;
};
const installArchive = async (
state: TerraformState,
opts?: { env?: string[] },
) => {
const instance = findResourceInstance(state, "coder_script");
const id = await prepareContainer();
// Run installer script with correct env for CODER_SCRIPT paths.
const args = ["bash"];
if (USE_XTRACE) args.push("-x");
args.push("-c", instance.script);
const resp = await execContainer(id, args, [
"--env",
`CODER_SCRIPT_BIN_DIR=${BIN_DIR}`,
"--env",
`CODER_SCRIPT_DATA_DIR=${DATA_DIR}`,
...(opts?.env ?? []),
]);
return {
id,
install: {
exitCode: resp.exitCode,
stdout: resp.stdout.trim(),
stderr: resp.stderr.trim(),
},
};
};
const fileExists = async (id: string, path: string) => {
const res = await sh(id, `test -f ${path} && echo yes || echo no`);
return res.stdout.trim() === "yes";
};
const isExecutable = async (id: string, path: string) => {
const res = await sh(id, `test -x ${path} && echo yes || echo no`);
return res.stdout.trim() === "yes";
};
const listTar = async (id: string, path: string) => {
// Try to autodetect compression flags from extension.
let cmd = "";
if (path.endsWith(".tar.gz")) {
cmd = `tar -tzf ${path}`;
} else if (path.endsWith(".tar.zst")) {
// validate with zstd and ask tar to list via --zstd.
cmd = `zstd -t -q ${path} && tar --zstd -tf ${path}`;
} else {
cmd = `tar -tf ${path}`;
}
return sh(id, cmd);
};
describe("archive", () => {
beforeAll(async () => {
await runTerraformInit(import.meta.dir);
});
// Ensure required variables are enforced.
testRequiredVariables(import.meta.dir, {
agent_id: "agent-123",
});
it("installs wrapper scripts to BIN_DIR and library to DATA_DIR", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "agent-123",
});
// The Terraform output should reflect defaults from main.tf.
expect(state.outputs.archive_path.value).toEqual(
"/tmp/coder-archive.tar.gz",
);
const { id, install } = await installArchive(state);
ensureRunOk("install", install);
expect(install.stdout).toContain(
`Installed archive library to: ${DATA_DIR}/archive-lib.sh`,
);
expect(install.stdout).toContain(
`Installed create script to: ${BIN_DIR}/coder-archive-create`,
);
expect(install.stdout).toContain(
`Installed extract script to: ${BIN_DIR}/coder-archive-extract`,
);
expect(await isExecutable(id, `${BIN_DIR}/coder-archive-create`)).toBe(
true,
);
expect(await isExecutable(id, `${BIN_DIR}/coder-archive-extract`)).toBe(
true,
);
});
it("uses sane defaults: creates gzip archive at the default path and logs to stderr", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "agent-123",
// Keep defaults: compression=gzip, output_dir=/tmp, archive_name=coder-archive.
});
const { id } = await installArchive(state);
const createTestdata = await bashRun(
id,
`mkdir ~/gzip; touch ~/gzip/defaults.txt`,
);
ensureRunOk("create testdata", createTestdata);
const run = await bashRun(id, `${BIN_DIR}/coder-archive-create`);
ensureRunOk("archive-create default run", run);
// Only the archive path should print to stdout.
expect(run.stdout.trim()).toEqual("/tmp/coder-archive.tar.gz");
expect(await fileExists(id, "/tmp/coder-archive.tar.gz")).toBe(true);
// Some useful diagnostics should be on stderr.
expect(run.stderr).toContain("Creating archive:");
expect(run.stderr).toContain("Compression: gzip");
const list = await listTar(id, "/tmp/coder-archive.tar.gz");
ensureRunOk("list default archive", list);
expect(list.stdout).toContain("gzip/defaults.txt");
}, 20000);
it("creates a gzip archive with explicit -f and includes extra CLI paths", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "agent-123",
// Provide a simple default path so we can assert contents.
paths: `["~/gzip"]`,
compression: "gzip",
});
const { id } = await installArchive(state);
const createTestdata = await bashRun(
id,
`mkdir ~/gzip; touch ~/gzip/test.txt; touch ~/gziptest.txt`,
);
ensureRunOk("create testdata", createTestdata);
const out = "/tmp/backup/test-archive.tar.gz";
const run = await bashRun(
id,
`${BIN_DIR}/coder-archive-create -f ${out} ~/gziptest.txt`,
);
ensureRunOk("archive-create gzip explicit -f", run);
expect(run.stdout.trim()).toEqual(out);
expect(await fileExists(id, out)).toBe(true);
const list = await sh(id, `tar -tzf ${out}`);
ensureRunOk("tar -tzf contents (gzip)", list);
expect(list.stdout).toContain("gzip/test.txt");
expect(list.stdout).toContain("gziptest.txt");
}, 20000);
it("creates a zstd-compressed archive when requested via CLI override", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "agent-123",
paths: `["/etc/hostname"]`,
// Module default is gzip, override at runtime to zstd.
});
const { id } = await installArchive(state);
const out = "/tmp/backup/zstd-archive.tar.zst";
const run = await bashRun(
id,
`${BIN_DIR}/coder-archive-create --compression zstd -f ${out}`,
);
ensureRunOk("archive-create zstd", run);
expect(run.stdout.trim()).toEqual(out);
// Check integrity via zstd and that tar can list it.
ensureRunOk("zstd -t", await sh(id, `test -f ${out} && zstd -t -q ${out}`));
ensureRunOk("tar --zstd -tf", await sh(id, `tar --zstd -tf ${out}`));
}, 30000);
it("creates an uncompressed tar when compression=none", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "agent-123",
// Keep module defaults but override at runtime.
});
const { id } = await installArchive(state);
const out = "/tmp/backup/raw-archive.tar";
const run = await bashRun(
id,
`${BIN_DIR}/coder-archive-create --compression none -f ${out}`,
);
ensureRunOk("archive-create none", run);
expect(run.stdout.trim()).toEqual(out);
ensureRunOk("tar -tf (none)", await sh(id, `tar -tf ${out} >/dev/null`));
}, 20000);
it("applies exclude patterns from Terraform", async () => {
// Include a file, but also exclude it via Terraform defaults to ensure
// exclusion flows through.
const state = await runTerraformApply(import.meta.dir, {
agent_id: "agent-123",
paths: `["/etc/hostname"]`,
exclude_patterns: `["/etc/hostname"]`,
});
const { id } = await installArchive(state);
const out = "/tmp/backup/excluded.tar.gz";
const run = await bashRun(id, `${BIN_DIR}/coder-archive-create -f ${out}`);
ensureRunOk("archive-create with exclude_patterns", run);
const list = await sh(id, `tar -tzf ${out}`);
ensureRunOk("tar -tzf contents (exclude)", list);
expect(list.stdout).not.toContain("etc/hostname"); // Excluded by Terraform default.
}, 20000);
it("adds a run_on_stop script when enabled", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "agent-123",
create_on_stop: true,
});
const coderScripts = state.resources.filter(
(r) => r.type === "coder_script",
);
// Installer (run_on_start) + run_on_stop.
expect(coderScripts.length).toBe(2);
});
it("extracts a previously created archive into a target directory", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "agent-123",
paths: `["/etc/hostname"]`,
compression: "gzip",
});
const { id } = await installArchive(state);
// Create archive.
const out = "/tmp/backup/extract-test.tar.gz";
const created = await bashRun(
id,
`${BIN_DIR}/coder-archive-create -f ${out} /etc/hosts`,
);
ensureRunOk("create for extract", created);
// Extract archive.
const extractDir = "/tmp/extract";
const extract = await bashRun(
id,
`${BIN_DIR}/coder-archive-extract -f ${out} -C ${extractDir}`,
);
ensureRunOk("archive-extract", extract);
// Verify a known file exists after extraction.
const exists = await sh(
id,
`test -f ${extractDir}/etc/hosts && echo ok || echo no`,
);
expect(exists.stdout.trim()).toEqual("ok");
}, 20000);
it("honors Terraform defaults without CLI args (compression, name, output_dir)", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "agent-123",
compression: "zstd",
archive_name: "my-default",
output_dir: "/tmp/defout",
});
const { id } = await installArchive(state);
const run = await bashRun(id, `${BIN_DIR}/coder-archive-create`);
ensureRunOk("archive-create terraform defaults", run);
expect(run.stdout.trim()).toEqual("/tmp/defout/my-default.tar.zst");
expect(run.stderr).toContain("Creating archive:");
expect(run.stderr).toContain("Compression: zstd");
ensureRunOk(
"zstd -t",
await sh(id, "zstd -t -q /tmp/defout/my-default.tar.zst"),
);
ensureRunOk(
"tar --zstd -tf",
await sh(id, "tar --zstd -tf /tmp/defout/my-default.tar.zst"),
);
}, 30000);
});
+134
View File
@@ -0,0 +1,134 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 0.12"
}
}
}
variable "agent_id" {
description = "The ID of a Coder agent."
type = string
}
variable "paths" {
description = "List of files/directories to include in the archive. Defaults to the current directory."
type = list(string)
default = ["."]
}
variable "exclude_patterns" {
description = "Exclude patterns for the archive."
type = list(string)
default = []
}
variable "compression" {
description = "Compression algorithm for the archive. Supported: gzip, zstd, none."
type = string
default = "gzip"
validation {
condition = contains(["gzip", "zstd", "none"], var.compression)
error_message = "compression must be one of: gzip, zstd, none."
}
}
variable "archive_name" {
description = "Optional archive base name without extension. If empty, defaults to \"coder-archive\"."
type = string
default = "coder-archive"
}
variable "output_dir" {
description = "Optional output directory where the archive will be written. Defaults to \"/tmp\"."
type = string
default = "/tmp"
}
variable "directory" {
description = "Change current directory to this path before creating or extracting the archive. Defaults to the user's home directory."
type = string
default = "~"
}
variable "create_on_stop" {
description = "If true, also create a run_on_stop script that creates the archive automatically on workspace stop."
type = bool
default = false
}
variable "extract_on_start" {
description = "If true, the installer will wait for an archive and extract it on start."
type = bool
default = false
}
variable "extract_wait_timeout_seconds" {
description = "Timeout (seconds) to wait for an archive when extract_on_start is true."
type = number
default = 5
}
# Provide a stable script filename and sensible defaults.
locals {
extension = var.compression == "gzip" ? ".tar.gz" : var.compression == "zstd" ? ".tar.zst" : ".tar"
# Ensure ~ is expanded because it cannot be expanded inside quotes in a
# templated shell script.
paths = [for v in var.paths : replace(v, "/^~(\\/|$)/", "$$HOME$1")]
exclude_patterns = [for v in var.exclude_patterns : replace(v, "/^~(\\/|$)/", "$$HOME$1")]
directory = replace(var.directory, "/^~(\\/|$)/", "$$HOME$1")
output_dir = replace(var.output_dir, "/^~(\\/|$)/", "$$HOME$1")
archive_path = "${local.output_dir}/${var.archive_name}${local.extension}"
}
output "archive_path" {
description = "Full path to the archive file that will be created, extracted, or both."
value = local.archive_path
}
# This script installs the user-facing archive script into $CODER_SCRIPT_BIN_DIR.
# The installed script can be run manually by the user to create an archive.
resource "coder_script" "archive_start_script" {
agent_id = var.agent_id
display_name = "Archive"
icon = "/icon/folder.svg"
run_on_start = true
start_blocks_login = var.extract_on_start
# Render the user-facing archive script with Terraform defaults, then write it to $CODER_SCRIPT_BIN_DIR
script = templatefile("${path.module}/run.sh", {
TF_LIB_B64 = base64encode(file("${path.module}/scripts/archive-lib.sh")),
TF_PATHS = join(" ", formatlist("%q", local.paths)),
TF_EXCLUDE_PATTERNS = join(" ", formatlist("%q", local.exclude_patterns)),
TF_COMPRESSION = var.compression,
TF_ARCHIVE_PATH = local.archive_path,
TF_DIRECTORY = local.directory,
TF_EXTRACT_ON_START = var.extract_on_start,
TF_EXTRACT_WAIT_TIMEOUT = var.extract_wait_timeout_seconds,
})
}
# Optionally, also register a run_on_stop script that creates the archive automatically
# when the workspace stops. It simply invokes the installed archive script.
resource "coder_script" "archive_stop_script" {
count = var.create_on_stop ? 1 : 0
agent_id = var.agent_id
display_name = "Archive"
icon = "/icon/folder.svg"
run_on_stop = true
start_blocks_login = false
# Call the installed script. It will log to stderr and print the archive path to stdout.
# We redirect stdout to stderr to avoid surfacing the path in system logs if undesired.
# Remove the redirection if you want the path to appear in stdout on stop as well.
script = <<-EOT
#!/usr/bin/env bash
set -euo pipefail
"$CODER_SCRIPT_BIN_DIR/coder-archive-create"
EOT
}
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
set -euo pipefail
LIB_B64="${TF_LIB_B64}"
EXTRACT_ON_START="${TF_EXTRACT_ON_START}"
EXTRACT_WAIT_TIMEOUT="${TF_EXTRACT_WAIT_TIMEOUT}"
# Set script defaults from Terraform.
IFS=' ' read -r -a DEFAULT_PATHS <<< "${TF_PATHS}"
IFS=' ' read -r -a DEFAULT_EXCLUDE_PATTERNS <<< "${TF_EXCLUDE_PATTERNS}"
DEFAULT_COMPRESSION="${TF_COMPRESSION}"
DEFAULT_ARCHIVE_PATH="${TF_ARCHIVE_PATH}"
DEFAULT_DIRECTORY="${TF_DIRECTORY}"
# 1) Decode the library into $CODER_SCRIPT_DATA_DIR/archive-lib.sh (static, sourceable).
LIB_PATH="$CODER_SCRIPT_DATA_DIR/archive-lib.sh"
lib_tmp="$(mktemp -t coder-module-archive.XXXXXX))"
trap 'rm -f "$lib_tmp" 2>/dev/null || true' EXIT
# Decode the base64 content safely.
if ! printf '%s' "$LIB_B64" | base64 -d > "$lib_tmp"; then
echo "ERROR: Failed to decode archive library from base64." >&2
exit 1
fi
chmod 0644 "$lib_tmp"
mv "$lib_tmp" "$LIB_PATH"
# 2) Generate the wrapper scripts (create and extract).
create_wrapper() {
tmp="$(mktemp -t coder-module-archive.XXXXXX)"
trap 'rm -f "$tmp" 2>/dev/null || true' EXIT
cat > "$tmp" << EOF
#!/usr/bin/env bash
set -euo pipefail
. "$LIB_PATH"
# Set defaults from Terraform (through installer).
$(
declare -p \
DEFAULT_PATHS \
DEFAULT_EXCLUDE_PATTERNS \
DEFAULT_COMPRESSION \
DEFAULT_ARCHIVE_PATH \
DEFAULT_DIRECTORY
)
$1 "\$@"
EOF
chmod 0755 "$tmp"
mv "$tmp" "$2"
}
CREATE_WRAPPER_PATH="$CODER_SCRIPT_BIN_DIR/coder-archive-create"
EXTRACT_WRAPPER_PATH="$CODER_SCRIPT_BIN_DIR/coder-archive-extract"
create_wrapper archive_create "$CREATE_WRAPPER_PATH"
create_wrapper archive_extract "$EXTRACT_WRAPPER_PATH"
echo "Installed archive library to: $LIB_PATH"
echo "Installed create script to: $CREATE_WRAPPER_PATH"
echo "Installed extract script to: $EXTRACT_WRAPPER_PATH"
# 3) Optionally wait for and extract an archive on start.
if [[ $EXTRACT_ON_START = true ]]; then
# shellcheck disable=SC1090
. "$LIB_PATH"
archive_wait_and_extract "$EXTRACT_WAIT_TIMEOUT" quiet || {
exit_code=$?
if [[ $exit_code -eq 2 ]]; then
echo "WARNING: Archive not found in backup path (this is expected with new workspaces)."
else
exit $exit_code
fi
}
fi
@@ -0,0 +1,279 @@
#!/usr/bin/env bash
set -euo pipefail
log() {
printf '%s\n' "$@" >&2
}
warn() {
printf 'WARNING: %s\n' "$1" >&2
}
error() {
printf 'ERROR: %s\n' "$1" >&2
exit 1
}
load_defaults() {
DEFAULT_PATHS=("${DEFAULT_PATHS[@]:-.}")
DEFAULT_EXCLUDE_PATTERNS=("${DEFAULT_EXCLUDE_PATTERNS[@]:-}")
DEFAULT_COMPRESSION="${DEFAULT_COMPRESSION:-gzip}"
DEFAULT_ARCHIVE_PATH="${DEFAULT_ARCHIVE_PATH:-/tmp/coder-archive.tar.gz}"
DEFAULT_DIRECTORY="${DEFAULT_DIRECTORY:-$HOME}"
}
ensure_tools() {
command -v tar > /dev/null 2>&1 || error "tar is required"
case "$1" in
gzip)
command -v gzip > /dev/null 2>&1 || error "gzip is required for gzip compression"
;;
zstd)
command -v zstd > /dev/null 2>&1 || error "zstd is required for zstd compression"
;;
none) ;;
*)
error "Unsupported compression algorithm: $1"
;;
esac
}
usage_archive_create() {
load_defaults
cat >&2 << USAGE
Usage: coder-archive-create [OPTIONS] [[PATHS] ...]
Options:
-c, --compression <gzip|zstd|none> Compression algorithm (default "${DEFAULT_COMPRESSION}")
-C, --directory <DIRECTORY> Change to directory (default "${DEFAULT_DIRECTORY}")
-f, --file <ARCHIVE> Output archive file (default "${DEFAULT_ARCHIVE_PATH}")
-h, --help Show this help
USAGE
}
archive_create() {
load_defaults
local compression="${DEFAULT_COMPRESSION}"
local directory="${DEFAULT_DIRECTORY}"
local file="${DEFAULT_ARCHIVE_PATH}"
local paths=("${DEFAULT_PATHS[@]}")
while [[ $# -gt 0 ]]; do
case "$1" in
-c | --compression)
if [[ $# -lt 2 ]]; then
usage_archive_create
error "Missing value for $1"
fi
compression="$2"
shift 2
;;
-C | --directory)
if [[ $# -lt 2 ]]; then
usage_archive_create
error "Missing value for $1"
fi
directory="$2"
shift 2
;;
-f | --file)
if [[ $# -lt 2 ]]; then
usage_archive_create
error "Missing value for $1"
fi
file="$2"
shift 2
;;
-h | --help)
usage_archive_create
exit 0
;;
--)
shift
while [[ $# -gt 0 ]]; do
paths+=("$1")
shift
done
;;
-*)
usage_archive_create
error "Unknown option: $1"
;;
*)
paths+=("$1")
shift
;;
esac
done
ensure_tools "$compression"
local -a tar_opts=(-c -f "$file" -C "$directory")
case "$compression" in
gzip)
tar_opts+=(-z)
;;
zstd)
tar_opts+=(--zstd)
;;
none) ;;
*)
error "Unsupported compression algorithm: $compression"
;;
esac
for path in "${DEFAULT_EXCLUDE_PATTERNS[@]}"; do
if [[ -n $path ]]; then
tar_opts+=(--exclude "$path")
fi
done
# Ensure destination directory exists.
dest="$(dirname "$file")"
mkdir -p "$dest" 2> /dev/null || error "Failed to create output dir: $dest"
log "Creating archive:"
log " Compression: $compression"
log " Directory: $directory"
log " Archive: $file"
log " Paths: ${paths[*]}"
log " Exclude: ${DEFAULT_EXCLUDE_PATTERNS[*]}"
umask 077
tar "${tar_opts[@]}" "${paths[@]}"
printf '%s\n' "$file"
}
usage_archive_extract() {
load_defaults
cat >&2 << USAGE
Usage: coder-archive-extract [OPTIONS]
Options:
-c, --compression <gzip|zstd|none> Compression algorithm (default "${DEFAULT_COMPRESSION}")
-C, --directory <DIRECTORY> Change to directory (default "${DEFAULT_DIRECTORY}")
-f, --file <ARCHIVE> Output archive file (default "${DEFAULT_ARCHIVE_PATH}")
-h, --help Show this help
USAGE
}
archive_extract() {
load_defaults
local compression="${DEFAULT_COMPRESSION}"
local directory="${DEFAULT_DIRECTORY}"
local file="${DEFAULT_ARCHIVE_PATH}"
while [[ $# -gt 0 ]]; do
case "$1" in
-c | --compression)
if [[ $# -lt 2 ]]; then
usage_archive_extract
error "Missing value for $1"
fi
compression="$2"
shift 2
;;
-C | --directory)
if [[ $# -lt 2 ]]; then
usage_archive_extract
error "Missing value for $1"
fi
directory="$2"
shift 2
;;
-f | --file)
if [[ $# -lt 2 ]]; then
usage_archive_extract
error "Missing value for $1"
fi
file="$2"
shift 2
;;
-h | --help)
usage_archive_extract
exit 0
;;
--)
shift
while [[ $# -gt 0 ]]; do
shift
done
;;
-*)
usage_archive_extract
error "Unknown option: $1"
;;
*)
shift
;;
esac
done
ensure_tools "$compression"
local -a tar_opts=(-x -f "$file" -C "$directory")
case "$compression" in
gzip)
tar_opts+=(-z)
;;
zstd)
tar_opts+=(--zstd)
;;
none) ;;
*)
error "Unsupported compression algorithm: $compression"
;;
esac
for path in "${DEFAULT_EXCLUDE_PATTERNS[@]}"; do
if [[ -n $path ]]; then
tar_opts+=(--exclude "$path")
fi
done
# Ensure destination directory exists.
mkdir -p "$directory" || error "Failed to create directory: $directory"
log "Extracting archive:"
log " Compression: $compression"
log " Directory: $directory"
log " Archive: $file"
log " Exclude: ${DEFAULT_EXCLUDE_PATTERNS[*]}"
umask 077
tar "${tar_opts[@]}" "${paths[@]}"
printf 'Extracted %s into %s\n' "$file" "$directory"
}
archive_wait_and_extract() {
load_defaults
local timeout="${1:-300}"
local quiet="${2:-}"
local file="${DEFAULT_ARCHIVE_PATH}"
local start now
start=$(date +%s)
while true; do
if [[ -f "$file" ]]; then
archive_extract -f "$file"
return 0
fi
if ((timeout <= 0)); then
break
fi
now=$(date +%s)
if ((now - start >= timeout)); then
break
fi
sleep 5
done
if [[ -z $quiet ]]; then
printf 'ERROR: Timed out waiting for archive: %s\n' "$file" >&2
fi
return 2
}
+5 -5
View File
@@ -13,7 +13,7 @@ Run Auggie CLI in your workspace to access Augment's AI coding assistant with ad
```tf
module "auggie" {
source = "registry.coder.com/coder-labs/auggie/coder"
version = "0.2.0"
version = "0.3.0"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
}
@@ -41,13 +41,13 @@ data "coder_parameter" "ai_prompt" {
module "coder-login" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/coder-login/coder"
version = "1.0.31"
version = "0.2.2"
agent_id = coder_agent.example.id
}
module "auggie" {
source = "registry.coder.com/coder-labs/auggie/coder"
version = "0.2.0"
version = "0.3.0"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
@@ -57,7 +57,7 @@ module "auggie" {
EOF # Required for tasks
# Version
auggie_version = "0.3.0"
auggie_version = "0.2.2"
# Task configuration
ai_prompt = data.coder_parameter.ai_prompt.value
@@ -103,7 +103,7 @@ EOF
```tf
module "auggie" {
source = "registry.coder.com/coder-labs/auggie/coder"
version = "0.2.0"
version = "0.3.0"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
+9 -3
View File
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.7"
version = ">= 2.12"
}
}
}
@@ -174,13 +174,15 @@ locals {
install_script = file("${path.module}/scripts/install.sh")
start_script = file("${path.module}/scripts/start.sh")
module_dir_name = ".auggie-module"
folder = trimsuffix(var.folder, "/")
}
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "1.2.0"
version = "2.0.0"
agent_id = var.agent_id
folder = local.folder
web_app_slug = local.app_slug
web_app_order = var.order
web_app_group = var.group
@@ -227,4 +229,8 @@ module "agentapi" {
ARG_MCP_CONFIG='${var.mcp != null ? base64encode(replace(var.mcp, "'", "'\\''")) : ""}' \
/tmp/install.sh
EOT
}
}
output "task_app_id" {
value = module.agentapi.task_app_id
}
@@ -1,7 +1,10 @@
#!/bin/bash
set -euo pipefail
source "$HOME"/.bashrc
if [ -f "$HOME/.bashrc" ]; then
source "$HOME"/.bashrc
fi
set -euo pipefail
BOLD='\033[0;1m'
@@ -1,7 +1,10 @@
#!/bin/bash
set -euo pipefail
source "$HOME"/.bashrc
if [ -f "$HOME/.bashrc" ]; then
source "$HOME"/.bashrc
fi
set -euo pipefail
command_exists() {
command -v "$1" > /dev/null 2>&1
+12 -10
View File
@@ -13,10 +13,10 @@ Run Codex CLI in your workspace to access OpenAI's models through the Codex inte
```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "2.1.0"
version = "3.1.1"
agent_id = coder_agent.example.id
openai_api_key = var.openai_api_key
folder = "/home/coder/project"
workdir = "/home/coder/project"
}
```
@@ -33,10 +33,11 @@ module "codex" {
module "codex" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/codex/coder"
version = "2.1.0"
version = "3.1.1"
agent_id = coder_agent.example.id
openai_api_key = "..."
folder = "/home/coder/project"
workdir = "/home/coder/project"
report_tasks = false
}
```
@@ -54,17 +55,17 @@ data "coder_parameter" "ai_prompt" {
module "coder-login" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/coder-login/coder"
version = "1.0.31"
version = "3.1.1"
agent_id = coder_agent.example.id
}
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "2.1.0"
version = "3.1.1"
agent_id = coder_agent.example.id
openai_api_key = "..."
ai_prompt = data.coder_parameter.ai_prompt.value
folder = "/home/coder/project"
workdir = "/home/coder/project"
# Custom configuration for full auto mode
base_config_toml = <<-EOT
@@ -75,7 +76,7 @@ module "codex" {
```
> [!WARNING]
> This module configures Codex with a `workspace-write` sandbox that allows AI tasks to read/write files in the specified folder. While the sandbox provides security boundaries, Codex can still modify files within the workspace. Use this module _only_ in trusted environments and be aware of the security implications.
> This module configures Codex with a `workspace-write` sandbox that allows AI tasks to read/write files in the specified workdir. While the sandbox provides security boundaries, Codex can still modify files within the workspace. Use this module _only_ in trusted environments and be aware of the security implications.
## How it Works
@@ -83,6 +84,7 @@ module "codex" {
- **System Prompt**: If `codex_system_prompt` is set, writes the prompt to `AGENTS.md` in the `~/.codex/` directory
- **Start**: Launches Codex CLI in the specified directory, wrapped by AgentAPI
- **Configuration**: Sets `OPENAI_API_KEY` environment variable and passes `--model` flag to Codex CLI (if variables provided)
- **Session Continuity**: When `continue = true` (default), the module automatically tracks task sessions in `~/.codex-module/.codex-task-session`. On workspace restart, it resumes the existing session with full conversation history. Set `continue = false` to always start fresh sessions.
## Configuration
@@ -106,7 +108,7 @@ For custom Codex configuration, use `base_config_toml` and/or `additional_mcp_se
```tf
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "2.1.0"
version = "3.1.1"
# ... other variables ...
# Override default configuration
@@ -137,7 +139,7 @@ module "codex" {
> [!IMPORTANT]
> To use tasks with Codex CLI, ensure you have the `openai_api_key` variable set, and **you create a `coder_parameter` named `"AI Prompt"` and pass its value to the codex module's `ai_prompt` variable**. [Tasks Template Example](https://registry.coder.com/templates/coder-labs/tasks-docker).
> The module automatically configures Codex with your API key and model preferences.
> folder is a required variable for the module to function correctly.
> workdir is a required variable for the module to function correctly.
## References
+91 -5
View File
@@ -47,7 +47,7 @@ const setup = async (props?: SetupProps): Promise<{ id: string }> => {
install_codex: props?.skipCodexMock ? "true" : "false",
install_agentapi: props?.skipAgentAPIMock ? "true" : "false",
codex_model: "gpt-4-turbo",
folder: "/home/coder",
workdir: "/home/coder",
...props?.moduleVariables,
},
registerCleanup,
@@ -166,12 +166,12 @@ describe("codex", async () => {
expect(postInstallLog).toContain("post-install-script");
});
test("folder-variable", async () => {
const folder = "/tmp/codex-test-folder";
test("workdir-variable", async () => {
const workdir = "/tmp/codex-test-workdir";
const { id } = await setup({
skipCodexMock: false,
moduleVariables: {
folder,
workdir,
},
});
await execModuleScript(id);
@@ -179,7 +179,7 @@ describe("codex", async () => {
id,
"/home/coder/.codex-module/install.log",
);
expect(resp).toContain(folder);
expect(resp).toContain(workdir);
});
test("additional-mcp-servers", async () => {
@@ -368,4 +368,90 @@ describe("codex", async () => {
expect(prompt.exitCode).not.toBe(0);
expect(prompt.stderr).toContain("No such file or directory");
});
test("codex-continue-capture-new-session", async () => {
const { id } = await setup({
moduleVariables: {
continue: "true",
ai_prompt: "test task",
},
});
const workdir = "/home/coder";
const expectedSessionId = "019a1234-5678-9abc-def0-123456789012";
const sessionsDir = "/home/coder/.codex/sessions";
const sessionFile = `${sessionsDir}/${expectedSessionId}.jsonl`;
await execContainer(id, ["mkdir", "-p", sessionsDir]);
await execContainer(id, [
"bash",
"-c",
`echo '{"id":"${expectedSessionId}","cwd":"${workdir}","created":"2024-10-24T20:00:00Z","model":"gpt-4-turbo"}' > ${sessionFile}`,
]);
await execModuleScript(id);
await expectAgentAPIStarted(id);
const trackingFile = "/home/coder/.codex-module/.codex-task-session";
const maxAttempts = 30;
let trackingFileContents = "";
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const result = await execContainer(id, [
"bash",
"-c",
`cat ${trackingFile} 2>/dev/null || echo ""`,
]);
if (result.stdout.trim().length > 0) {
trackingFileContents = result.stdout;
break;
}
await new Promise((resolve) => setTimeout(resolve, 500));
}
expect(trackingFileContents).toContain(`${workdir}|${expectedSessionId}`);
const startLog = await readFileContainer(
id,
"/home/coder/.codex-module/agentapi-start.log",
);
expect(startLog).toContain("Capturing new session ID");
expect(startLog).toContain("Session tracked");
expect(startLog).toContain(expectedSessionId);
});
test("codex-continue-resume-existing-session", async () => {
const { id } = await setup({
moduleVariables: {
continue: "true",
ai_prompt: "test prompt",
},
});
const workdir = "/home/coder";
const mockSessionId = "019a1234-5678-9abc-def0-123456789012";
const trackingFile = "/home/coder/.codex-module/.codex-task-session";
await execContainer(id, ["mkdir", "-p", "/home/coder/.codex-module"]);
await execContainer(id, [
"bash",
"-c",
`echo "${workdir}|${mockSessionId}" > ${trackingFile}`,
]);
await execModuleScript(id);
const startLog = await execContainer(id, [
"bash",
"-c",
"cat /home/coder/.codex-module/agentapi-start.log",
]);
expect(startLog.stdout).toContain("Found existing task session");
expect(startLog.stdout).toContain(mockSessionId);
expect(startLog.stdout).toContain("Resuming existing session");
expect(startLog.stdout).toContain(
`Starting Codex with arguments: --model gpt-4-turbo resume ${mockSessionId}`,
);
expect(startLog.stdout).not.toContain("test prompt");
});
});
+50 -6
View File
@@ -36,11 +36,41 @@ variable "icon" {
default = "/icon/openai.svg"
}
variable "folder" {
variable "workdir" {
type = string
description = "The folder to run Codex in."
}
variable "report_tasks" {
type = bool
description = "Whether to enable task reporting to Coder UI via AgentAPI"
default = true
}
variable "subdomain" {
type = bool
description = "Whether to use a subdomain for AgentAPI."
default = false
}
variable "cli_app" {
type = bool
description = "Whether to create a CLI app for Codex"
default = false
}
variable "web_app_display_name" {
type = string
description = "Display name for the web app"
default = "Codex"
}
variable "cli_app_display_name" {
type = string
description = "Display name for the CLI app"
default = "Codex CLI"
}
variable "install_codex" {
type = bool
description = "Whether to install Codex."
@@ -107,6 +137,12 @@ variable "ai_prompt" {
default = ""
}
variable "continue" {
type = bool
description = "Automatically continue existing sessions on workspace restart. When true, resumes existing conversation if found, otherwise runs prompt or starts new session. When false, always starts fresh (ignores existing sessions)."
default = true
}
variable "codex_system_prompt" {
type = string
description = "System instructions written to AGENTS.md in the ~/.codex directory"
@@ -120,6 +156,7 @@ resource "coder_env" "openai_api_key" {
}
locals {
workdir = trimsuffix(var.workdir, "/")
app_slug = "codex"
install_script = file("${path.module}/scripts/install.sh")
start_script = file("${path.module}/scripts/start.sh")
@@ -131,15 +168,18 @@ module "agentapi" {
version = "1.2.0"
agent_id = var.agent_id
folder = local.workdir
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 = "Codex"
cli_app_slug = "${local.app_slug}-cli"
cli_app_display_name = "Codex CLI"
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
module_dir_name = local.module_dir_name
install_agentapi = var.install_agentapi
agentapi_subdomain = var.subdomain
agentapi_version = var.agentapi_version
pre_install_script = var.pre_install_script
post_install_script = var.post_install_script
@@ -151,9 +191,11 @@ module "agentapi" {
echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh
chmod +x /tmp/start.sh
ARG_OPENAI_API_KEY='${var.openai_api_key}' \
ARG_REPORT_TASKS='${var.report_tasks}' \
ARG_CODEX_MODEL='${var.codex_model}' \
ARG_CODEX_START_DIRECTORY='${var.folder}' \
ARG_CODEX_START_DIRECTORY='${local.workdir}' \
ARG_CODEX_TASK_PROMPT='${base64encode(var.ai_prompt)}' \
ARG_CONTINUE='${var.continue}' \
/tmp/start.sh
EOT
@@ -164,12 +206,14 @@ module "agentapi" {
echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh
chmod +x /tmp/install.sh
ARG_OPENAI_API_KEY='${var.openai_api_key}' \
ARG_REPORT_TASKS='${var.report_tasks}' \
ARG_INSTALL='${var.install_codex}' \
ARG_CODEX_VERSION='${var.codex_version}' \
ARG_BASE_CONFIG_TOML='${base64encode(var.base_config_toml)}' \
ARG_ADDITIONAL_MCP_SERVERS='${base64encode(var.additional_mcp_servers)}' \
ARG_CODER_MCP_APP_STATUS_SLUG='${local.app_slug}' \
ARG_CODEX_START_DIRECTORY='${var.folder}' \
ARG_CODEX_START_DIRECTORY='${local.workdir}' \
ARG_CODEX_INSTRUCTION_PROMPT='${base64encode(var.codex_system_prompt)}' \
/tmp/install.sh
EOT
@@ -22,6 +22,8 @@ printf "Start Directory: %s\n" "$ARG_CODEX_START_DIRECTORY"
printf "Has Base Config: %s\n" "$([ -n "$ARG_BASE_CONFIG_TOML" ] && echo "Yes" || echo "No")"
printf "Has Additional MCP: %s\n" "$([ -n "$ARG_ADDITIONAL_MCP_SERVERS" ] && echo "Yes" || echo "No")"
printf "Has System Prompt: %s\n" "$([ -n "$ARG_CODEX_INSTRUCTION_PROMPT" ] && echo "Yes" || echo "No")"
printf "OpenAI API Key: %s\n" "$([ -n "$ARG_OPENAI_API_KEY" ] && echo "Provided" || echo "Not provided")"
printf "Report Tasks: %s\n" "$ARG_REPORT_TASKS"
echo "======================================"
set +o nounset
@@ -100,13 +102,20 @@ EOF
append_mcp_servers_section() {
local config_path="$1"
if [ "${ARG_REPORT_TASKS}" == "false" ]; then
ARG_CODER_MCP_APP_STATUS_SLUG=""
CODER_MCP_AI_AGENTAPI_URL=""
else
CODER_MCP_AI_AGENTAPI_URL="http://localhost:3284"
fi
cat << EOF >> "$config_path"
# MCP Servers Configuration
[mcp_servers.Coder]
command = "coder"
args = ["exp", "mcp", "server"]
env = { "CODER_MCP_APP_STATUS_SLUG" = "${ARG_CODER_MCP_APP_STATUS_SLUG}", "CODER_MCP_AI_AGENTAPI_URL" = "http://localhost:3284", "CODER_AGENT_URL" = "${CODER_AGENT_URL}", "CODER_AGENT_TOKEN" = "${CODER_AGENT_TOKEN}" }
env = { "CODER_MCP_APP_STATUS_SLUG" = "${ARG_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}" }
description = "Report ALL tasks and statuses (in progress, done, failed) you are working on."
type = "stdio"
@@ -159,7 +168,21 @@ function add_instruction_prompt_if_exists() {
fi
}
function add_auth_json() {
AUTH_JSON_PATH="$HOME/.codex/auth.json"
mkdir -p "$(dirname "$AUTH_JSON_PATH")"
AUTH_JSON=$(
cat << EOF
{
"OPENAI_API_KEY": "${ARG_OPENAI_API_KEY}"
}
EOF
)
echo "$AUTH_JSON" > "$AUTH_JSON_PATH"
}
install_codex
codex --version
populate_config_toml
add_instruction_prompt_if_exists
add_auth_json
@@ -3,6 +3,7 @@
source "$HOME"/.bashrc
set -o errexit
set -o pipefail
command_exists() {
command -v "$1" > /dev/null 2>&1
}
@@ -16,55 +17,202 @@ fi
printf "Version: %s\n" "$(codex --version)"
set -o nounset
ARG_CODEX_TASK_PROMPT=$(echo -n "$ARG_CODEX_TASK_PROMPT" | base64 -d)
ARG_CONTINUE=${ARG_CONTINUE:-true}
echo "=== Codex Launch Configuration ==="
printf "OpenAI API Key: %s\n" "$([ -n "$ARG_OPENAI_API_KEY" ] && echo "Provided" || echo "Not provided")"
printf "Codex Model: %s\n" "${ARG_CODEX_MODEL:-"Default"}"
printf "Start Directory: %s\n" "$ARG_CODEX_START_DIRECTORY"
printf "Has Task Prompt: %s\n" "$([ -n "$ARG_CODEX_TASK_PROMPT" ] && echo "Yes" || echo "No")"
printf "Report Tasks: %s\n" "$ARG_REPORT_TASKS"
printf "Continue Sessions: %s\n" "$ARG_CONTINUE"
echo "======================================"
set +o nounset
CODEX_ARGS=()
if command_exists codex; then
printf "Codex is installed\n"
else
printf "Error: Codex is not installed. Please enable install_codex or install it manually\n"
exit 1
fi
SESSION_TRACKING_FILE="$HOME/.codex-module/.codex-task-session"
if [ -d "${ARG_CODEX_START_DIRECTORY}" ]; then
printf "Directory '%s' exists. Changing to it.\\n" "${ARG_CODEX_START_DIRECTORY}"
cd "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not change to directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
find_session_for_directory() {
local target_dir="$1"
if [ ! -f "$SESSION_TRACKING_FILE" ]; then
return 1
fi
local session_id
session_id=$(grep "^$target_dir|" "$SESSION_TRACKING_FILE" | cut -d'|' -f2 | head -1)
if [ -n "$session_id" ]; then
echo "$session_id"
return 0
fi
return 1
}
store_session_mapping() {
local dir="$1"
local session_id="$2"
mkdir -p "$(dirname "$SESSION_TRACKING_FILE")"
if [ -f "$SESSION_TRACKING_FILE" ]; then
grep -v "^$dir|" "$SESSION_TRACKING_FILE" > "$SESSION_TRACKING_FILE.tmp" 2> /dev/null || true
mv "$SESSION_TRACKING_FILE.tmp" "$SESSION_TRACKING_FILE"
fi
echo "$dir|$session_id" >> "$SESSION_TRACKING_FILE"
}
find_recent_session_file() {
local target_dir="$1"
local sessions_dir="$HOME/.codex/sessions"
if [ ! -d "$sessions_dir" ]; then
return 1
fi
local latest_file=""
local latest_time=0
while IFS= read -r session_file; do
local file_time
file_time=$(stat -c %Y "$session_file" 2> /dev/null || stat -f %m "$session_file" 2> /dev/null || echo "0")
local first_line
first_line=$(head -n 1 "$session_file" 2> /dev/null)
local session_cwd
session_cwd=$(echo "$first_line" | grep -o '"cwd":"[^"]*"' | cut -d'"' -f4)
if [ "$session_cwd" = "$target_dir" ] && [ "$file_time" -gt "$latest_time" ]; then
latest_file="$session_file"
latest_time="$file_time"
fi
done < <(find "$sessions_dir" -type f -name "*.jsonl" 2> /dev/null)
if [ -n "$latest_file" ]; then
local first_line
first_line=$(head -n 1 "$latest_file")
local session_id
session_id=$(echo "$first_line" | grep -o '"id":"[^"]*"' | cut -d'"' -f4)
if [ -n "$session_id" ]; then
echo "$session_id"
return 0
fi
fi
return 1
}
wait_for_session_file() {
local target_dir="$1"
local max_attempts=20
local attempt=0
while [ $attempt -lt $max_attempts ]; do
local session_id
session_id=$(find_recent_session_file "$target_dir" 2> /dev/null || echo "")
if [ -n "$session_id" ]; then
echo "$session_id"
return 0
fi
sleep 0.5
attempt=$((attempt + 1))
done
return 1
}
validate_codex_installation() {
if command_exists codex; then
printf "Codex is installed\n"
else
printf "Error: Codex is not installed. Please enable install_codex or install it manually\n"
exit 1
}
else
printf "Directory '%s' does not exist. Creating and changing to it.\\n" "${ARG_CODEX_START_DIRECTORY}"
mkdir -p "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not create directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
exit 1
}
cd "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not change to directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
exit 1
}
fi
fi
}
if [ -n "$ARG_CODEX_MODEL" ]; then
CODEX_ARGS+=("--model" "$ARG_CODEX_MODEL")
fi
setup_workdir() {
if [ -d "${ARG_CODEX_START_DIRECTORY}" ]; then
printf "Directory '%s' exists. Changing to it.\\n" "${ARG_CODEX_START_DIRECTORY}"
cd "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not change to directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
exit 1
}
else
printf "Directory '%s' does not exist. Creating and changing to it.\\n" "${ARG_CODEX_START_DIRECTORY}"
mkdir -p "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not create directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
exit 1
}
cd "${ARG_CODEX_START_DIRECTORY}" || {
printf "Error: Could not change to directory '%s'.\\n" "${ARG_CODEX_START_DIRECTORY}"
exit 1
}
fi
}
if [ -n "$ARG_CODEX_TASK_PROMPT" ]; then
printf "Running the task prompt %s\n" "$ARG_CODEX_TASK_PROMPT"
PROMPT="Complete the task at hand in one go. Every step of the way, report your progress using coder_report_task tool with proper summary and statuses. Your task at hand: $ARG_CODEX_TASK_PROMPT"
CODEX_ARGS+=("$PROMPT")
else
printf "No task prompt given.\n"
fi
build_codex_args() {
CODEX_ARGS=()
# Terminal dimensions optimized for Coder Tasks UI sidebar:
# - Width 67: fits comfortably in sidebar
# - Height 1190: adjusted due to Codex terminal height bug
printf "Starting Codex with arguments: %s\n" "${CODEX_ARGS[*]}"
agentapi server --term-width 67 --term-height 1190 -- codex "${CODEX_ARGS[@]}"
if [ -n "$ARG_CODEX_MODEL" ]; then
CODEX_ARGS+=("--model" "$ARG_CODEX_MODEL")
fi
if [ "$ARG_CONTINUE" = "true" ]; then
existing_session=$(find_session_for_directory "$ARG_CODEX_START_DIRECTORY" 2> /dev/null || echo "")
if [ -n "$existing_session" ]; then
printf "Found existing task session for this directory: %s\n" "$existing_session"
printf "Resuming existing session...\n"
CODEX_ARGS+=("resume" "$existing_session")
else
printf "No existing task session found for this directory\n"
printf "Starting new task session...\n"
if [ -n "$ARG_CODEX_TASK_PROMPT" ]; then
if [ "${ARG_REPORT_TASKS}" == "true" ]; then
PROMPT="Complete the task at hand in one go. Every step of the way, report your progress using coder_report_task tool with proper summary and statuses. Your task at hand: $ARG_CODEX_TASK_PROMPT"
else
PROMPT="Your task at hand: $ARG_CODEX_TASK_PROMPT"
fi
CODEX_ARGS+=("$PROMPT")
fi
fi
else
printf "Continue disabled, starting fresh session\n"
if [ -n "$ARG_CODEX_TASK_PROMPT" ]; then
if [ "${ARG_REPORT_TASKS}" == "true" ]; then
PROMPT="Complete the task at hand in one go. Every step of the way, report your progress using coder_report_task tool with proper summary and statuses. Your task at hand: $ARG_CODEX_TASK_PROMPT"
else
PROMPT="Your task at hand: $ARG_CODEX_TASK_PROMPT"
fi
CODEX_ARGS+=("$PROMPT")
fi
fi
}
capture_session_id() {
if [ "$ARG_CONTINUE" = "true" ] && [ -z "$existing_session" ]; then
printf "Capturing new session ID...\n"
new_session=$(wait_for_session_file "$ARG_CODEX_START_DIRECTORY" || echo "")
if [ -n "$new_session" ]; then
store_session_mapping "$ARG_CODEX_START_DIRECTORY" "$new_session"
printf "✓ Session tracked: %s\n" "$new_session"
printf "This session will be automatically resumed on next restart\n"
else
printf "⚠ Could not capture session ID after 10s timeout\n"
fi
fi
}
start_codex() {
printf "Starting Codex with arguments: %s\n" "${CODEX_ARGS[*]}"
agentapi server --term-width 67 --term-height 1190 -- codex "${CODEX_ARGS[@]}" &
capture_session_id
}
validate_codex_installation
setup_workdir
build_codex_args
start_codex
+25 -1
View File
@@ -1,5 +1,6 @@
#!/bin/bash
# Handle --version flag
if [[ "$1" == "--version" ]]; then
echo "HELLO: $(bash -c env)"
echo "codex version v1.0.0"
@@ -8,7 +9,30 @@ fi
set -e
SESSION_ID=""
IS_RESUME=false
while [[ $# -gt 0 ]]; do
case $1 in
resume)
IS_RESUME=true
SESSION_ID="$2"
shift 2
;;
*)
shift
;;
esac
done
if [ "$IS_RESUME" = false ]; then
SESSION_ID="019a1234-5678-9abc-def0-123456789012"
echo "Created new session: $SESSION_ID"
else
echo "Resuming session: $SESSION_ID"
fi
while true; do
echo "$(date) - codex-mock"
echo "$(date) - codex-mock (session: $SESSION_ID)"
sleep 15
done
@@ -13,7 +13,7 @@ Run [GitHub Copilot CLI](https://docs.github.com/copilot/concepts/agents/about-c
```tf
module "copilot" {
source = "registry.coder.com/coder-labs/copilot/coder"
version = "0.2.1"
version = "0.3.0"
agent_id = coder_agent.example.id
workdir = "/home/coder/projects"
}
@@ -51,7 +51,7 @@ data "coder_parameter" "ai_prompt" {
module "copilot" {
source = "registry.coder.com/coder-labs/copilot/coder"
version = "0.2.1"
version = "0.3.0"
agent_id = coder_agent.example.id
workdir = "/home/coder/projects"
@@ -71,12 +71,12 @@ Customize tool permissions, MCP servers, and Copilot settings:
```tf
module "copilot" {
source = "registry.coder.com/coder-labs/copilot/coder"
version = "0.2.1"
version = "0.3.0"
agent_id = coder_agent.example.id
workdir = "/home/coder/projects"
# Version pinning (defaults to "0.0.334", use "latest" for newest version)
copilot_version = "latest"
# Version pinning (defaults to "latest", use specific version if desired)
copilot_version = "0.2.3"
# Tool permissions
allow_tools = ["shell(git)", "shell(npm)", "write"]
@@ -142,7 +142,7 @@ variable "github_token" {
module "copilot" {
source = "registry.coder.com/coder-labs/copilot/coder"
version = "0.2.1"
version = "0.3.0"
agent_id = coder_agent.example.id
workdir = "/home/coder/projects"
github_token = var.github_token
@@ -156,7 +156,7 @@ Run Copilot as a command-line tool without task reporting or web interface. This
```tf
module "copilot" {
source = "registry.coder.com/coder-labs/copilot/coder"
version = "0.2.1"
version = "0.3.0"
agent_id = coder_agent.example.id
workdir = "/home/coder"
report_tasks = false
+10 -6
View File
@@ -3,7 +3,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.7"
version = ">= 2.12"
}
}
}
@@ -104,7 +104,7 @@ variable "agentapi_version" {
variable "copilot_version" {
type = string
description = "The version of GitHub Copilot CLI to install. Use 'latest' for the latest version or specify a version like '0.0.334'."
default = "0.0.334"
default = "latest"
}
variable "report_tasks" {
@@ -242,7 +242,7 @@ resource "coder_env" "github_token" {
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "1.2.0"
version = "2.0.0"
agent_id = var.agent_id
folder = local.workdir
@@ -268,7 +268,7 @@ module "agentapi" {
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_SYSTEM_PROMPT='${base64encode(local.final_system_prompt)}' \
@@ -288,7 +288,7 @@ module "agentapi" {
set -o pipefail
echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh
chmod +x /tmp/install.sh
ARG_MCP_APP_STATUS_SLUG='${local.app_slug}' \
ARG_REPORT_TASKS='${var.report_tasks}' \
ARG_WORKDIR='${local.workdir}' \
@@ -299,4 +299,8 @@ module "agentapi" {
ARG_COPILOT_MODEL='${var.copilot_model}' \
/tmp/install.sh
EOT
}
}
output "task_app_id" {
value = module.agentapi.task_app_id
}
@@ -1,7 +1,10 @@
#!/bin/bash
set -euo pipefail
source "$HOME"/.bashrc
if [ -f "$HOME/.bashrc" ]; then
source "$HOME"/.bashrc
fi
set -euo pipefail
command_exists() {
command -v "$1" > /dev/null 2>&1
@@ -1,7 +1,11 @@
#!/bin/bash
if [ -f "$HOME/.bashrc" ]; then
source "$HOME"/.bashrc
fi
set -euo pipefail
source "$HOME"/.bashrc
export PATH="$HOME/.local/bin:$PATH"
command_exists() {
@@ -13,8 +13,8 @@ Run the Cursor Agent CLI in your workspace for interactive coding assistance and
```tf
module "cursor_cli" {
source = "registry.coder.com/coder-labs/cursor-cli/coder"
version = "0.2.0"
agent_id = coder_agent.example.id
version = "0.3.0"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
}
```
@@ -42,8 +42,8 @@ module "coder-login" {
module "cursor_cli" {
source = "registry.coder.com/coder-labs/cursor-cli/coder"
version = "0.2.0"
agent_id = coder_agent.example.id
version = "0.3.0"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
# Optional
@@ -60,6 +60,7 @@ module "cursor_cli" {
command = "npx"
args = ["-y", "@playwright/mcp@latest", "--headless", "--isolated", "--no-sandbox"]
}
desktop-commander = {
command = "npx"
args = ["-y", "@wonderwhy-er/desktop-commander"]
@@ -159,7 +159,7 @@ describe("cursor-cli", async () => {
"-c",
"cat /home/coder/.cursor-cli-module/agentapi-start.log || cat /home/coder/.cursor-cli-module/start.log || true",
]);
expect(startLog.stdout).toContain(`-m ${model}`);
expect(startLog.stdout).toContain(`--model ${model}`);
expect(startLog.stdout).toContain("-f");
expect(startLog.stdout).toContain("test prompt");
});
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.7"
version = ">= 2.12"
}
}
}
@@ -113,6 +113,7 @@ locals {
install_script = file("${path.module}/scripts/install.sh")
start_script = file("${path.module}/scripts/start.sh")
module_dir_name = ".cursor-cli-module"
folder = trimsuffix(var.folder, "/")
}
# Expose status slug and API key to the agent environment
@@ -131,9 +132,10 @@ resource "coder_env" "cursor_api_key" {
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "1.2.0"
version = "2.0.0"
agent_id = var.agent_id
folder = local.folder
web_app_slug = local.app_slug
web_app_order = var.order
web_app_group = var.group
@@ -177,3 +179,7 @@ module "agentapi" {
/tmp/install.sh
EOT
}
output "task_app_id" {
value = module.agentapi.task_app_id
}
@@ -50,7 +50,7 @@ ARGS=()
# global flags
if [ -n "$ARG_MODEL" ]; then
ARGS+=("-m" "$ARG_MODEL")
ARGS+=("--model" "$ARG_MODEL")
fi
if [ "$ARG_FORCE" = "true" ]; then
ARGS+=("-f")
+9 -9
View File
@@ -13,8 +13,8 @@ Run [Gemini CLI](https://github.com/google-gemini/gemini-cli) in your workspace
```tf
module "gemini" {
source = "registry.coder.com/coder-labs/gemini/coder"
version = "2.1.0"
agent_id = coder_agent.example.id
version = "3.0.0"
agent_id = coder_agent.main.id
folder = "/home/coder/project"
}
```
@@ -46,8 +46,8 @@ variable "gemini_api_key" {
module "gemini" {
source = "registry.coder.com/coder-labs/gemini/coder"
version = "2.1.0"
agent_id = coder_agent.example.id
version = "3.0.0"
agent_id = coder_agent.main.id
gemini_api_key = var.gemini_api_key
folder = "/home/coder/project"
}
@@ -80,7 +80,7 @@ module "coder-login" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/coder-login/coder"
version = "~> 1.0"
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
}
data "coder_parameter" "ai_prompt" {
@@ -94,8 +94,8 @@ data "coder_parameter" "ai_prompt" {
module "gemini" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/gemini/coder"
version = "2.1.0"
agent_id = coder_agent.example.id
version = "3.0.0"
agent_id = coder_agent.main.id
gemini_api_key = var.gemini_api_key
gemini_model = "gemini-2.5-flash"
folder = "/home/coder/project"
@@ -118,8 +118,8 @@ For enterprise users who prefer Google's Vertex AI platform:
```tf
module "gemini" {
source = "registry.coder.com/coder-labs/gemini/coder"
version = "2.1.0"
agent_id = coder_agent.example.id
version = "3.0.0"
agent_id = coder_agent.main.id
gemini_api_key = var.gemini_api_key
folder = "/home/coder/project"
use_vertexai = true
+9 -3
View File
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.7"
version = ">= 2.12"
}
}
}
@@ -172,13 +172,15 @@ EOT
install_script = file("${path.module}/scripts/install.sh")
start_script = file("${path.module}/scripts/start.sh")
module_dir_name = ".gemini-module"
folder = trimsuffix(var.folder, "/")
}
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "1.2.0"
version = "2.0.0"
agent_id = var.agent_id
folder = local.folder
web_app_slug = local.app_slug
web_app_order = var.order
web_app_group = var.group
@@ -223,4 +225,8 @@ module "agentapi" {
GEMINI_TASK_PROMPT='${var.task_prompt}' \
/tmp/start.sh
EOT
}
}
output "task_app_id" {
value = module.agentapi.task_app_id
}
@@ -16,7 +16,7 @@ A module that adds Nextflow to your Coder template.
module "nextflow" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/nextflow/coder"
version = "0.9.0"
agent_id = coder_agent.example.id
version = "0.9.1"
agent_id = coder_agent.main.id
}
```
@@ -0,0 +1,64 @@
---
display_name: Open WebUI
description: A self-hosted AI chat interface supporting various LLM providers
icon: ../../../../.icons/openwebui.svg
verified: false
tags: [ai, llm, chat, web, python]
---
# Open WebUI
Open WebUI is a user-friendly web interface for interacting with Large Language Models. It provides a ChatGPT-like interface that can connect to various LLM providers including OpenAI, Ollama, and more.
```tf
module "open-webui" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/open-webui/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
}
```
![Open WebUI](../../.images/openwebui.png)
## Prerequisites
- **Python 3.11 or higher** must be installed in your image (with `venv` module)
- Port 7800 (default) or your custom port must be available
For Ubuntu/Debian, you can install Python 3.11 from [deadsnakes PPA](https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa):
```shell
sudo add-apt-repository -y ppa:deadsnakes/ppa
sudo apt-get update
sudo apt-get install -y python3.11 python3.11-venv
```
## Examples
### With OpenAI API Key
```tf
module "open-webui" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/open-webui/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
openai_api_key = var.openai_api_key
}
```
### Custom Port and Data Directory
```tf
module "open-webui" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/open-webui/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
http_server_port = 8080
data_dir = "/home/coder/open-webui-data"
}
```
@@ -0,0 +1,94 @@
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 "http_server_log_path" {
type = string
description = "The path to log Open WebUI to."
default = "/tmp/open-webui.log"
}
variable "http_server_port" {
type = number
description = "The port to run Open WebUI on."
default = 7800
}
variable "open_webui_version" {
type = string
description = "The version of Open WebUI to install"
default = "latest"
}
variable "data_dir" {
type = string
description = "The directory where Open WebUI stores its data (database, uploads, vector_db, cache)."
default = ".open-webui"
}
variable "openai_api_key" {
type = string
description = "OpenAI API key for accessing OpenAI models. If not provided, OpenAI integration will need to be configured manually in the UI."
default = ""
sensitive = true
}
variable "share" {
type = string
description = "The sharing level for the Open WebUI app. Set to 'owner' for private access, 'authenticated' for access by any authenticated user, or 'public' for public access."
default = "owner"
validation {
condition = var.share == "owner" || var.share == "authenticated" || var.share == "public"
error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'."
}
}
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
}
resource "coder_script" "open-webui" {
agent_id = var.agent_id
display_name = "open-webui"
icon = "/icon/openwebui.svg"
script = templatefile("${path.module}/run.sh", {
HTTP_SERVER_LOG_PATH : var.http_server_log_path,
HTTP_SERVER_PORT : var.http_server_port,
VERSION : var.open_webui_version,
DATA_DIR : var.data_dir,
OPENAI_API_KEY : var.openai_api_key,
})
run_on_start = true
}
resource "coder_app" "open-webui" {
agent_id = var.agent_id
slug = "open-webui"
display_name = "Open WebUI"
url = "http://localhost:${var.http_server_port}"
icon = "/icon/openwebui.svg"
subdomain = true
share = var.share
order = var.order
group = var.group
}
@@ -0,0 +1,188 @@
mock_provider "coder" {}
run "test_defaults" {
command = plan
variables {
agent_id = "test-agent-123"
}
assert {
condition = var.http_server_port == 7800
error_message = "Default port should be 7800"
}
assert {
condition = var.http_server_log_path == "/tmp/open-webui.log"
error_message = "Default log path should be /tmp/open-webui.log"
}
assert {
condition = var.share == "owner"
error_message = "Default share should be 'owner'"
}
assert {
condition = var.open_webui_version == "latest"
error_message = "Default version should be 'latest'"
}
assert {
condition = coder_app.open-webui.subdomain == true
error_message = "App should use subdomain"
}
assert {
condition = coder_app.open-webui.display_name == "Open WebUI"
error_message = "App display name should be 'Open WebUI'"
}
}
run "test_custom_port" {
command = plan
variables {
agent_id = "test-agent-456"
http_server_port = 9000
}
assert {
condition = var.http_server_port == 9000
error_message = "Custom port should be 9000"
}
assert {
condition = coder_app.open-webui.url == "http://localhost:9000"
error_message = "App URL should use custom port"
}
}
run "test_custom_log_path" {
command = plan
variables {
agent_id = "test-agent-789"
http_server_log_path = "/var/log/open-webui.log"
}
assert {
condition = var.http_server_log_path == "/var/log/open-webui.log"
error_message = "Custom log path should be set"
}
}
run "test_share_authenticated" {
command = plan
variables {
agent_id = "test-agent-auth"
share = "authenticated"
}
assert {
condition = coder_app.open-webui.share == "authenticated"
error_message = "Share should be 'authenticated'"
}
}
run "test_share_public" {
command = plan
variables {
agent_id = "test-agent-public"
share = "public"
}
assert {
condition = coder_app.open-webui.share == "public"
error_message = "Share should be 'public'"
}
}
run "test_order_and_group" {
command = plan
variables {
agent_id = "test-agent-order"
order = 10
group = "AI Tools"
}
assert {
condition = coder_app.open-webui.order == 10
error_message = "Order should be 10"
}
assert {
condition = coder_app.open-webui.group == "AI Tools"
error_message = "Group should be 'AI Tools'"
}
}
run "test_custom_version" {
command = plan
variables {
agent_id = "test-agent-version"
open_webui_version = "0.5.0"
}
assert {
condition = var.open_webui_version == "0.5.0"
error_message = "Custom version should be '0.5.0'"
}
}
run "test_custom_data_dir" {
command = plan
variables {
agent_id = "test-agent-data"
data_dir = "/home/coder/open-webui-data"
}
assert {
condition = var.data_dir == "/home/coder/open-webui-data"
error_message = "Custom data_dir should be set"
}
}
run "test_default_data_dir" {
command = plan
variables {
agent_id = "test-agent-data-default"
}
assert {
condition = var.data_dir == ".open-webui"
error_message = "Default data_dir should be '.open-webui'"
}
}
run "test_openai_api_key" {
command = plan
variables {
agent_id = "test-agent-openai"
openai_api_key = "sk-test-key-123"
}
assert {
condition = var.openai_api_key == "sk-test-key-123"
error_message = "OpenAI API key should be set"
}
}
run "test_default_openai_api_key" {
command = plan
variables {
agent_id = "test-agent-openai-default"
}
assert {
condition = var.openai_api_key == ""
error_message = "Default OpenAI API key should be empty"
}
}
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env sh
set -eu
printf '\033[0;1mInstalling Open WebUI %s...\n\n' "${VERSION}"
check_python_version() {
python_cmd="$1"
if command -v "$python_cmd" > /dev/null 2>&1; then
version=$("$python_cmd" --version 2>&1 | awk '{print $2}')
major=$(echo "$version" | cut -d. -f1)
minor=$(echo "$version" | cut -d. -f2)
if [ "$major" -eq 3 ] && [ "$minor" -ge 11 ]; then
echo "$python_cmd"
return 0
fi
fi
return 1
}
PYTHON_CMD=""
for cmd in python3.13 python3.12 python3.11 python3 python; do
if result=$(check_python_version "$cmd"); then
PYTHON_CMD="$result"
echo "✅ Found suitable Python: $PYTHON_CMD ($($PYTHON_CMD --version 2>&1))"
break
fi
done
if [ -z "$PYTHON_CMD" ]; then
echo "❌ Python 3.11 or higher is required but not found."
echo ""
echo "Please install Python 3.11+ in your image. For example on Ubuntu/Debian:"
echo " sudo add-apt-repository -y ppa:deadsnakes/ppa"
echo " sudo apt-get update"
echo " sudo apt-get install -y python3.11 python3.11-venv"
exit 1
fi
VENV_DIR="$HOME/.open-webui-venv"
if [ ! -d "$VENV_DIR" ]; then
echo "📦 Creating virtual environment..."
"$PYTHON_CMD" -m venv "$VENV_DIR"
fi
. "$VENV_DIR/bin/activate"
if ! pip show open-webui > /dev/null 2>&1; then
echo "📦 Installing Open WebUI version ${VERSION}..."
if [ "${VERSION}" = "latest" ]; then
pip install open-webui
else
pip install "open-webui==${VERSION}"
fi
echo "🥳 Open WebUI has been installed"
else
echo "✅ Open WebUI is already installed"
fi
echo "👷 Starting Open WebUI in background..."
echo "Check logs at ${HTTP_SERVER_LOG_PATH}"
DATA_DIR="${DATA_DIR}" \
OPENAI_API_KEY="${OPENAI_API_KEY}" \
open-webui serve --host 0.0.0.0 --port "${HTTP_SERVER_PORT}" > "${HTTP_SERVER_LOG_PATH}" 2>&1 &
echo "🥳 Open WebUI is ready. HTTP server is listening on port ${HTTP_SERVER_PORT}"
@@ -0,0 +1,109 @@
---
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.1"
agent_id = coder_agent.main.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.1"
agent_id = coder_agent.main.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.1"
agent_id = coder_agent.main.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
View File
@@ -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
View File
@@ -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
@@ -0,0 +1,55 @@
---
display_name: Perplexica
description: Run Perplexica AI search engine in your workspace via Docker
icon: ../../../../.icons/perplexica.svg
verified: false
tags: [ai, search, docker]
---
# Perplexica
Run [Perplexica](https://github.com/ItzCrazyKns/Perplexica), a privacy-focused AI search engine, in your Coder workspace. Supports cloud providers (OpenAI, Anthropic Claude) and local LLMs via Ollama.
```tf
module "perplexica" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/perplexica/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
}
```
This module uses the full Perplexica image with embedded SearXNG for simpler setup with no external dependencies.
![Perplexica](../../.images/perplexica.png)
## Prerequisites
This module requires Docker to be available on the host.
## Examples
### With API Keys
```tf
module "perplexica" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/perplexica/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
openai_api_key = var.openai_api_key
anthropic_api_key = var.anthropic_api_key
}
```
### With Local Ollama
```tf
module "perplexica" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/perplexica/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
ollama_api_url = "http://ollama-external-endpoint:11434"
}
```
@@ -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 "docker_socket" {
type = string
description = "(Optional) Docker socket URI"
default = ""
}
variable "port" {
type = number
description = "The port to run Perplexica on."
default = 3000
}
variable "data_path" {
type = string
description = "Host path to mount for Perplexica data persistence."
default = "./perplexica-data"
}
variable "uploads_path" {
type = string
description = "Host path to mount for Perplexica file uploads."
default = "./perplexica-uploads"
}
variable "openai_api_key" {
type = string
description = "OpenAI API key."
default = ""
sensitive = true
}
variable "anthropic_api_key" {
type = string
description = "Anthropic API key for Claude models."
default = ""
sensitive = true
}
variable "ollama_api_url" {
type = string
description = "Ollama API URL for local LLM support."
default = ""
}
variable "share" {
type = string
default = "owner"
validation {
condition = var.share == "owner" || var.share == "authenticated" || var.share == "public"
error_message = "Incorrect value. Please set either 'owner', 'authenticated', or 'public'."
}
}
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
}
resource "coder_script" "perplexica" {
agent_id = var.agent_id
display_name = "Perplexica"
icon = "/icon/perplexica.svg"
script = templatefile("${path.module}/run.sh", {
DOCKER_HOST : var.docker_socket,
PORT : var.port,
DATA_PATH : var.data_path,
UPLOADS_PATH : var.uploads_path,
OPENAI_API_KEY : var.openai_api_key,
ANTHROPIC_API_KEY : var.anthropic_api_key,
OLLAMA_API_URL : var.ollama_api_url,
})
run_on_start = true
}
resource "coder_app" "perplexica" {
agent_id = var.agent_id
slug = "perplexica"
display_name = "Perplexica"
url = "http://localhost:${var.port}"
icon = "/icon/perplexica.svg"
subdomain = true
share = var.share
order = var.order
group = var.group
}
@@ -0,0 +1,26 @@
run "plan_basic" {
command = plan
variables {
agent_id = "test-agent"
}
assert {
condition = resource.coder_app.perplexica.url == "http://localhost:3000"
error_message = "Default port should be 3000"
}
}
run "plan_custom_port" {
command = plan
variables {
agent_id = "test-agent"
port = 8080
}
assert {
condition = resource.coder_app.perplexica.url == "http://localhost:8080"
error_message = "Should use custom port"
}
}
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env sh
set -eu
BOLD='\033[0;1m'
RESET='\033[0m'
printf "$${BOLD}Starting Perplexica...$${RESET}\n"
# Set Docker host if provided
if [ -n "${DOCKER_HOST}" ]; then
export DOCKER_HOST="${DOCKER_HOST}"
fi
# Wait for docker to become ready
max_attempts=10
delay=2
attempt=1
while ! docker ps; do
if [ $attempt -ge $max_attempts ]; then
echo "Failed to list containers after $${max_attempts} attempts."
exit 1
fi
echo "Attempt $${attempt} failed, retrying in $${delay}s..."
sleep $delay
attempt=$(expr "$attempt" + 1)
delay=$(expr "$delay" \* 2)
done
# Pull the image
IMAGE="itzcrazykns1337/perplexica:latest"
docker pull "$${IMAGE}"
# Build docker run command
DOCKER_ARGS="-d --rm --name perplexica -p ${PORT}:3000"
# Add mounts - convert relative paths to absolute
DATA_PATH="${DATA_PATH}"
UPLOADS_PATH="${UPLOADS_PATH}"
mkdir -p "$${DATA_PATH}"
mkdir -p "$${UPLOADS_PATH}"
DATA_PATH_ABS=$(cd "$${DATA_PATH}" && pwd)
UPLOADS_PATH_ABS=$(cd "$${UPLOADS_PATH}" && pwd)
DOCKER_ARGS="$${DOCKER_ARGS} -v $${DATA_PATH_ABS}:/home/perplexica/data"
DOCKER_ARGS="$${DOCKER_ARGS} -v $${UPLOADS_PATH_ABS}:/home/perplexica/uploads"
# Add environment variables if provided
if [ -n "${OPENAI_API_KEY}" ]; then
DOCKER_ARGS="$${DOCKER_ARGS} -e OPENAI_API_KEY=${OPENAI_API_KEY}"
fi
if [ -n "${ANTHROPIC_API_KEY}" ]; then
DOCKER_ARGS="$${DOCKER_ARGS} -e ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}"
fi
if [ -n "${OLLAMA_API_URL}" ]; then
DOCKER_ARGS="$${DOCKER_ARGS} -e OLLAMA_API_URL=${OLLAMA_API_URL}"
fi
# Run container
docker run $${DOCKER_ARGS} "$${IMAGE}"
printf "\n$${BOLD}Perplexica is running on port ${PORT}$${RESET}\n"
@@ -1,5 +1,5 @@
---
display_name: Amp CLI
display_name: Amp
icon: ../../../../.icons/sourcegraph-amp.svg
description: Sourcegraph's AI coding agent with deep codebase understanding and intelligent code search capabilities
verified: true
@@ -12,19 +12,21 @@ Run [Amp CLI](https://ampcode.com/) in your workspace to access Sourcegraph's AI
```tf
module "amp-cli" {
source = "registry.coder.com/coder-labs/sourcegraph-amp/coder"
version = "1.1.0"
agent_id = coder_agent.example.id
sourcegraph_amp_api_key = var.sourcegraph_amp_api_key
install_sourcegraph_amp = true
agentapi_version = "latest"
source = "registry.coder.com/coder-labs/sourcegraph-amp/coder"
version = "3.0.0"
agent_id = coder_agent.example.id
amp_api_key = var.amp_api_key
install_amp = true
agentapi_version = "latest"
}
```
## Prerequisites
- Include the [Coder Login](https://registry.coder.com/modules/coder-login/coder) module in your template
- Node.js and npm are automatically installed (via NVM) if not already available
- **Default (official installer)**: No prerequisites - the official installer includes its own runtime (Bun)
- **npm installation (`install_via_npm = true`)**: Requires Node.js and npm to be installed before Amp installation
- Required for Alpine Linux or other musl-based systems
- Ensure Node.js and npm are available in your workspace image or via earlier provisioning steps
## Usage Example
@@ -35,52 +37,55 @@ data "coder_parameter" "ai_prompt" {
type = "string"
default = ""
mutable = true
}
# Set system prompt for Amp CLI via environment variables
resource "coder_agent" "main" {
# ...
env = {
SOURCEGRAPH_AMP_SYSTEM_PROMPT = <<-EOT
You are an Amp assistant that helps developers debug and write code efficiently.
Always log task status to Coder.
EOT
SOURCEGRAPH_AMP_TASK_PROMPT = data.coder_parameter.ai_prompt.value
}
}
variable "sourcegraph_amp_api_key" {
variable "amp_api_key" {
type = string
description = "Sourcegraph Amp API key. Get one at https://ampcode.com/settings"
sensitive = true
}
module "amp-cli" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/sourcegraph-amp/coder"
version = "1.1.0"
agent_id = coder_agent.example.id
sourcegraph_amp_api_key = var.sourcegraph_amp_api_key # recommended for authenticated usage
install_sourcegraph_amp = true
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/sourcegraph-amp/coder"
amp_version = "3.0.0"
agent_id = coder_agent.example.id
amp_api_key = var.amp_api_key # recommended for tasks usage
workdir = "/home/coder/project"
instruction_prompt = <<-EOT
# Instructions
- Start every response with `amp > `
EOT
ai_prompt = data.coder_parameter.ai_prompt.value
base_amp_config = jsonencode({
"amp.anthropic.thinking.enabled" = true
"amp.todos.enabled" = true
"amp.tools.stopTimeout" = 600
"amp.git.commit.ampThread.enabled" = true
"amp.git.commit.coauthor.enabled" = true
"amp.terminal.commands.nodeSpawn.loadProfile" = "daily"
"amp.permissions" = [
{ "tool" : "mcp__coder__*", "action" : "allow" },
{ "tool" : "Bash", "action" : "allow", "context" : "thread" },
{ "tool" : "Bash", "matches" : { "cmd" : ["rm -rf /*", "rm -rf ~/*"] }, "action" : "reject", "context" : "subagent" },
{ "tool" : "edit_file", "action" : "allow" },
{ "tool" : "write_file", "action" : "allow" },
{ "tool" : "read_file", "action" : "allow" },
{ "tool" : "Grep", "action" : "allow" }
]
})
}
```
## How it Works
- **Install**: Installs Sourcegraph Amp CLI using npm (installs Node.js via NVM if required)
- **Start**: Launches Amp CLI in the specified directory, wrapped with AgentAPI to enable tasks and AI interactions
- **Environment Variables**: Sets `SOURCEGRAPH_AMP_API_KEY` and `SOURCEGRAPH_AMP_START_DIRECTORY` for the CLI execution
## Troubleshooting
- If `amp` is not found, ensure `install_sourcegraph_amp = true` and your API key is valid
- Logs are written under `/home/coder/.sourcegraph-amp-module/` (`install.log`, `agentapi-start.log`) for debugging
- If `amp` is not found, ensure `install_amp = true` and your API key is valid
- Logs are written under `/home/coder/.amp-module/` (`install.log`, `agentapi-start.log`) for debugging
- If AgentAPI fails to start, verify that your container has network access and executable permissions for the scripts
> [!IMPORTANT]
> For using **Coder Tasks** with Amp CLI, make sure to pass the `AI Prompt` parameter and set `sourcegraph_amp_api_key`.
> To use tasks with Amp CLI, create a `coder_parameter` named `"AI Prompt"` and pass its value to the amp-cli module's `ai_prompt` variable. The `folder` variable is required for the module to function correctly.
> For using **Coder Tasks** with Amp CLI, make sure to set `amp_api_key`.
> This ensures task reporting and status updates work seamlessly.
## References
@@ -43,9 +43,9 @@ const setup = async (props?: SetupProps): Promise<{ id: string }> => {
const { id } = await setupUtil({
moduleDir: import.meta.dir,
moduleVariables: {
install_sourcegraph_amp: props?.skipAmpMock ? "true" : "false",
workdir: "/home/coder",
install_amp: props?.skipAmpMock ? "true" : "false",
install_agentapi: props?.skipAgentAPIMock ? "true" : "false",
sourcegraph_amp_model: "test-model",
...props?.moduleVariables,
},
registerCleanup,
@@ -68,45 +68,95 @@ const setup = async (props?: SetupProps): Promise<{ id: string }> => {
setDefaultTimeout(60 * 1000);
describe("sourcegraph-amp", async () => {
describe("amp", async () => {
beforeAll(async () => {
await runTerraformInit(import.meta.dir);
});
test("happy-path", async () => {
const { id } = await setup();
// test("happy-path", async () => {
// const { id } = await setup();
// await execModuleScript(id);
// await expectAgentAPIStarted(id);
// });
//
// test("api-key", async () => {
// const apiKey = "test-api-key-123";
// const { id } = await setup({
// moduleVariables: {
// amp_api_key: apiKey,
// },
// });
// await execModuleScript(id);
// const resp = await readFileContainer(
// id,
// "/home/coder/.amp-module/agentapi-start.log",
// );
// expect(resp).toContain("amp_api_key provided !");
// });
//
test("install-latest-version", async () => {
const { id } = await setup({
skipAmpMock: true,
skipAgentAPIMock: true,
moduleVariables: {
amp_version: "",
},
});
await execModuleScript(id);
await expectAgentAPIStarted(id);
});
test("api-key", async () => {
const apiKey = "test-api-key-123";
test("install-specific-version", async () => {
const { id } = await setup({
skipAmpMock: true,
moduleVariables: {
sourcegraph_amp_api_key: apiKey,
install_via_npm: "true",
amp_version: "0.0.1755964909-g31e083",
},
});
await execModuleScript(id);
const resp = await readFileContainer(
id,
"/home/coder/.sourcegraph-amp-module/agentapi-start.log",
"/home/coder/.amp-module/agentapi-start.log",
);
expect(resp).toContain("sourcegraph_amp_api_key provided !");
expect(resp).toContain("0.0.1755964909-g31e08");
});
test("custom-folder", async () => {
const folder = "/tmp/sourcegraph-amp-test";
test("install-via-npm", async () => {
const { id } = await setup({
skipAmpMock: true,
moduleVariables: {
install_via_npm: "true",
},
});
await execModuleScript(id);
const installLog = await readFileContainer(
id,
"/home/coder/.amp-module/install.log",
);
expect(installLog).toContain("Installing Amp via npm");
const startLog = await readFileContainer(
id,
"/home/coder/.amp-module/agentapi-start.log",
);
expect(startLog).toContain("AMP version:");
});
test("custom-workdir", async () => {
const workdir = "/tmp/amp-test";
const { id } = await setup({
moduleVariables: {
folder,
workdir,
},
});
await execModuleScript(id);
const resp = await readFileContainer(
id,
"/home/coder/.sourcegraph-amp-module/install.log",
"/home/coder/.amp-module/agentapi-start.log",
);
expect(resp).toContain(folder);
expect(resp).toContain(workdir);
});
test("pre-post-install-scripts", async () => {
@@ -119,39 +169,104 @@ describe("sourcegraph-amp", async () => {
await execModuleScript(id);
const preLog = await readFileContainer(
id,
"/home/coder/.sourcegraph-amp-module/pre_install.log",
"/home/coder/.amp-module/pre_install.log",
);
expect(preLog).toContain("pre-install-script");
const postLog = await readFileContainer(
id,
"/home/coder/.sourcegraph-amp-module/post_install.log",
"/home/coder/.amp-module/post_install.log",
);
expect(postLog).toContain("post-install-script");
});
test("system-prompt", async () => {
const prompt = "this is a system prompt for AMP";
const { id } = await setup();
await execModuleScript(id, {
SOURCEGRAPH_AMP_SYSTEM_PROMPT: prompt,
test("instruction-prompt", async () => {
const prompt = "this is a instruction prompt for AMP";
const { id } = await setup({
moduleVariables: {
instruction_prompt: prompt,
},
});
const resp = await readFileContainer(
id,
"/home/coder/.sourcegraph-amp-module/SYSTEM_PROMPT.md",
);
await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/.config/AGENTS.md");
expect(resp).toContain(prompt);
});
test("task-prompt", async () => {
test("ai-prompt", async () => {
const prompt = "this is a task prompt for AMP";
const { id } = await setup();
await execModuleScript(id, {
SOURCEGRAPH_AMP_TASK_PROMPT: prompt,
const { id } = await setup({
moduleVariables: {
ai_prompt: prompt,
},
});
await execModuleScript(id);
const resp = await readFileContainer(
id,
"/home/coder/.sourcegraph-amp-module/agentapi-start.log",
"/home/coder/.amp-module/agentapi-start.log",
);
expect(resp).toContain(`sourcegraph amp task prompt provided : ${prompt}`);
expect(resp).toContain(`amp task prompt provided : ${prompt}`);
});
test("custom-base-config", async () => {
const customConfig = JSON.stringify({
"amp.anthropic.thinking.enabled": false,
"amp.todos.enabled": false,
"amp.tools.stopTimeout": 900,
"amp.git.commit.ampThread.enabled": true,
});
const customMcp = JSON.stringify({
"test-server": {
command: "/usr/bin/test-mcp",
args: ["--test-arg"],
type: "stdio",
},
});
const { id } = await setup({
moduleVariables: {
base_amp_config: customConfig,
mcp: customMcp,
},
});
await execModuleScript(id, {
CODER_AGENT_TOKEN: "test-token",
CODER_AGENT_URL: "http://test-url:3000",
});
const settingsContent = await readFileContainer(
id,
"/home/coder/.config/amp/settings.json",
);
const settings = JSON.parse(settingsContent);
expect(settings["amp.anthropic.thinking.enabled"]).toBe(false);
expect(settings["amp.todos.enabled"]).toBe(false);
expect(settings["amp.tools.stopTimeout"]).toBe(900);
expect(settings["amp.git.commit.ampThread.enabled"]).toBe(true);
expect(settings["amp.mcpServers"]).toBeDefined();
expect(settings["amp.mcpServers"].coder).toBeDefined();
expect(settings["amp.mcpServers"]["test-server"]).toBeDefined();
expect(settings["amp.mcpServers"]["test-server"].command).toBe(
"/usr/bin/test-mcp",
);
expect(settings["amp.mcpServers"]["test-server"].args).toEqual([
"--test-arg",
]);
});
test("default-base-config", async () => {
const { id } = await setup();
await execModuleScript(id, {
CODER_AGENT_TOKEN: "test-token",
CODER_AGENT_URL: "http://test-url:3000",
});
const settingsContent = await readFileContainer(
id,
"/home/coder/.config/amp/settings.json",
);
const settings = JSON.parse(settingsContent);
expect(settings["amp.anthropic.thinking.enabled"]).toBe(true);
expect(settings["amp.todos.enabled"]).toBe(true);
expect(settings["amp.mcpServers"]).toBeDefined();
expect(settings["amp.mcpServers"].coder).toBeDefined();
expect(settings["amp.mcpServers"].coder.command).toBe("coder");
});
});
@@ -4,9 +4,14 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.7"
version = ">= 2.12"
}
external = {
source = "hashicorp/external"
version = "2.3.5"
}
}
}
variable "agent_id" {
@@ -36,28 +41,9 @@ variable "icon" {
default = "/icon/sourcegraph-amp.svg"
}
variable "folder" {
variable "workdir" {
type = string
description = "The folder to run sourcegraph_amp in."
default = "/home/coder"
}
variable "install_sourcegraph_amp" {
type = bool
description = "Whether to install sourcegraph-amp."
default = true
}
variable "sourcegraph_amp_api_key" {
type = string
description = "sourcegraph-amp API Key"
default = ""
}
resource "coder_env" "sourcegraph_amp_api_key" {
agent_id = var.agent_id
name = "SOURCEGRAPH_AMP_API_KEY"
value = var.sourcegraph_amp_api_key
description = "The folder to run AMP CLI in."
}
variable "install_agentapi" {
@@ -69,26 +55,92 @@ variable "install_agentapi" {
variable "agentapi_version" {
type = string
description = "The version of AgentAPI to install."
default = "v0.10.0"
default = "v0.11.1"
}
variable "cli_app" {
type = bool
description = "Whether to create a CLI app for Claude Code"
default = false
}
variable "web_app_display_name" {
type = string
description = "Display name for the web app"
default = "Amp"
}
variable "cli_app_display_name" {
type = string
description = "Display name for the CLI app"
default = "Amp CLI"
}
variable "pre_install_script" {
type = string
description = "Custom script to run before installing sourcegraph_amp"
description = "Custom script to run before installing amp cli"
default = null
}
variable "post_install_script" {
type = string
description = "Custom script to run after installing sourcegraph_amp."
description = "Custom script to run after installing amp cli."
default = null
}
variable "report_tasks" {
type = bool
description = "Whether to enable task reporting to Coder UI"
default = true
}
variable "install_amp" {
type = bool
description = "Whether to install amp cli."
default = true
}
variable "install_via_npm" {
type = bool
description = "Install Amp via npm instead of the official installer."
default = false
}
variable "amp_api_key" {
type = string
description = "amp cli API Key"
default = ""
}
variable "amp_version" {
type = string
description = "The version of amp cli to install."
default = ""
}
variable "ai_prompt" {
type = string
description = "Task prompt for the Amp CLI"
default = ""
}
variable "instruction_prompt" {
type = string
description = "Instruction prompt for the Amp CLI. https://ampcode.com/manual#AGENTS.md"
default = ""
}
resource "coder_env" "amp_api_key" {
agent_id = var.agent_id
name = "AMP_API_KEY"
value = var.amp_api_key
}
variable "base_amp_config" {
type = string
description = <<-EOT
Base AMP configuration in JSON format. Can be overridden to customize AMP settings.
If empty, defaults enable thinking and todos for autonomous operation. Additional options include:
- "amp.permissions": [] (tool permissions)
- "amp.tools.stopTimeout": 600 (extend timeout for long operations)
@@ -96,28 +148,42 @@ variable "base_amp_config" {
- "amp.tools.disable": ["builtin:open"] (disable tools for containers)
- "amp.git.commit.ampThread.enabled": true (link commits to threads)
- "amp.git.commit.coauthor.enabled": true (add Amp as co-author)
Reference: https://ampcode.com/manual
EOT
default = ""
}
variable "additional_mcp_servers" {
variable "mcp" {
type = string
description = "Additional MCP servers configuration in JSON format to append to amp.mcpServers."
default = null
}
variable "mode" {
type = string
description = "Set the agent mode (free, rush, smart) — controls the model, system prompt, and tool selection. Default: smart"
default = "smart"
validation {
condition = contains(["", "free", "rush", "smart"], var.mode)
error_message = "Invalid mode. Select one from (free, rush, smart)"
}
}
data "external" "env" {
program = ["sh", "-c", "echo '{\"CODER_AGENT_TOKEN\":\"'$CODER_AGENT_TOKEN'\",\"CODER_AGENT_URL\":\"'$CODER_AGENT_URL'\"}'"]
}
locals {
app_slug = "amp"
default_base_config = {
default_base_config = jsonencode({
"amp.anthropic.thinking.enabled" = true
"amp.todos.enabled" = true
}
"amp.terminal.animation" = false
})
# Use provided config or default, then extract base settings (excluding mcpServers)
user_config = var.base_amp_config != "" ? jsondecode(var.base_amp_config) : local.default_base_config
user_config = jsondecode(var.base_amp_config != "" ? var.base_amp_config : local.default_base_config)
base_amp_settings = { for k, v in local.user_config : k => v if k != "amp.mcpServers" }
coder_mcp = {
@@ -125,14 +191,16 @@ locals {
"command" = "coder"
"args" = ["exp", "mcp", "server"]
"env" = {
"CODER_MCP_APP_STATUS_SLUG" = local.app_slug
"CODER_MCP_AI_AGENTAPI_URL" = "http://localhost:3284"
"CODER_MCP_APP_STATUS_SLUG" = var.report_tasks == true ? local.app_slug : ""
"CODER_MCP_AI_AGENTAPI_URL" = var.report_tasks == true ? "http://localhost:3284" : ""
"CODER_AGENT_TOKEN" = data.external.env.result.CODER_AGENT_TOKEN
"CODER_AGENT_URL" = data.external.env.result.CODER_AGENT_URL
}
"type" = "stdio"
}
}
additional_mcp = var.additional_mcp_servers != null ? jsondecode(var.additional_mcp_servers) : {}
additional_mcp = var.mcp != null ? jsondecode(var.mcp) : {}
merged_mcp_servers = merge(
lookup(local.user_config, "amp.mcpServers", {}),
@@ -146,21 +214,24 @@ locals {
install_script = file("${path.module}/scripts/install.sh")
start_script = file("${path.module}/scripts/start.sh")
module_dir_name = ".sourcegraph-amp-module"
module_dir_name = ".amp-module"
workdir = trimsuffix(var.workdir, "/")
}
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "1.2.0"
version = "2.0.0"
agent_id = var.agent_id
folder = local.workdir
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 = "Sourcegraph Amp"
cli_app_slug = "${local.app_slug}-cli"
cli_app_display_name = "Sourcegraph Amp CLI"
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
module_dir_name = local.module_dir_name
install_agentapi = var.install_agentapi
agentapi_version = var.agentapi_version
@@ -173,8 +244,11 @@ module "agentapi" {
echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh
chmod +x /tmp/start.sh
SOURCEGRAPH_AMP_API_KEY='${var.sourcegraph_amp_api_key}' \
SOURCEGRAPH_AMP_START_DIRECTORY='${var.folder}' \
ARG_AMP_API_KEY='${var.amp_api_key}' \
ARG_AMP_START_DIRECTORY='${var.workdir}' \
ARG_AMP_TASK_PROMPT='${base64encode(var.ai_prompt)}' \
ARG_REPORT_TASKS='${var.report_tasks}' \
ARG_MODE='${var.mode}' \
/tmp/start.sh
EOT
@@ -185,11 +259,15 @@ module "agentapi" {
echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh
chmod +x /tmp/install.sh
ARG_INSTALL_SOURCEGRAPH_AMP='${var.install_sourcegraph_amp}' \
SOURCEGRAPH_AMP_START_DIRECTORY='${var.folder}' \
ARG_AMP_CONFIG="$(echo -n '${base64encode(jsonencode(local.final_config))}' | base64 -d)" \
ARG_INSTALL_AMP='${var.install_amp}' \
ARG_INSTALL_VIA_NPM='${var.install_via_npm}' \
ARG_AMP_CONFIG="${base64encode(jsonencode(local.final_config))}" \
ARG_AMP_VERSION='${var.amp_version}' \
ARG_AMP_INSTRUCTION_PROMPT='${base64encode(var.instruction_prompt)}' \
/tmp/install.sh
EOT
}
output "task_app_id" {
value = module.agentapi.task_app_id
}
@@ -3,75 +3,115 @@ set -euo pipefail
# ANSI colors
BOLD='\033[1m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
ARG_INSTALL_AMP=${ARG_INSTALL_AMP:-true}
ARG_INSTALL_VIA_NPM=${ARG_INSTALL_VIA_NPM:-false}
ARG_AMP_VERSION=${ARG_AMP_VERSION:-}
ARG_AMP_INSTRUCTION_PROMPT=$(echo -n "${ARG_AMP_INSTRUCTION_PROMPT:-}" | base64 -d)
ARG_AMP_CONFIG=$(echo -n "${ARG_AMP_CONFIG:-}" | base64 -d)
echo "--------------------------------"
echo "Install flag: $ARG_INSTALL_SOURCEGRAPH_AMP"
echo "Workspace: $SOURCEGRAPH_AMP_START_DIRECTORY"
printf "Install flag: %s\n" "$ARG_INSTALL_AMP"
printf "Install via npm: %s\n" "$ARG_INSTALL_VIA_NPM"
printf "Amp Version: %s\n" "$ARG_AMP_VERSION"
printf "AMP Config: %s\n" "$ARG_AMP_CONFIG"
printf "Instruction Prompt: %s\n" "$ARG_AMP_INSTRUCTION_PROMPT"
echo "--------------------------------"
# Helper function to check if a command exists
command_exists() {
command -v "$1" > /dev/null 2>&1
}
function install_node() {
if ! command_exists npm; then
printf "npm not found, checking for Node.js installation...\n"
if ! command_exists node; then
printf "Node.js not found, installing Node.js via NVM...\n"
export NVM_DIR="$HOME/.nvm"
if [ ! -d "$NVM_DIR" ]; then
mkdir -p "$NVM_DIR"
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
else
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
fi
install_amp_npm() {
printf "%s${YELLOW}Installing Amp via npm${NC}\n" "${BOLD}"
# Temporarily disable nounset (-u) for nvm to avoid PROVIDED_VERSION error
set +u
nvm install --lts
nvm use --lts
nvm alias default node
set -u
printf "Node.js installed: %s\n" "$(node --version)"
printf "npm installed: %s\n" "$(npm --version)"
else
printf "Node.js is installed but npm is not available. Please install npm manually.\n"
exit 1
fi
# Load nvm if available
# shellcheck source=/dev/null
if [ -f "$HOME/.nvm/nvm.sh" ]; then
source "$HOME/.nvm/nvm.sh"
fi
}
function install_sourcegraph_amp() {
if [ "${ARG_INSTALL_SOURCEGRAPH_AMP}" = "true" ]; then
install_node
# If nvm is not used, set up user npm global directory
if ! command_exists nvm; then
mkdir -p "$HOME/.npm-global"
npm config set prefix "$HOME/.npm-global"
export PATH="$HOME/.npm-global/bin:$PATH"
if ! grep -q "export PATH=$HOME/.npm-global/bin:\$PATH" ~/.bashrc; then
echo "export PATH=$HOME/.npm-global/bin:\$PATH" >> ~/.bashrc
fi
fi
printf "%s Installing Sourcegraph AMP CLI...\n" "${BOLD}"
npm install -g @sourcegraph/amp@0.0.1754179307-gba1f97
printf "%s Successfully installed Sourcegraph AMP CLI. Version: %s\n" "${BOLD}" "$(amp --version)"
if ! command_exists node || ! command_exists npm; then
printf "${YELLOW}Warning: Node.js/npm not found. Skipping Amp installation.${NC}\n"
printf "To install Amp via npm, please install Node.js and npm first.\n"
return 1
fi
}
function setup_system_prompt() {
if [ -n "${SOURCEGRAPH_AMP_SYSTEM_PROMPT:-}" ]; then
echo "Setting Sourcegraph AMP system prompt..."
mkdir -p "$HOME/.sourcegraph-amp-module"
echo "$SOURCEGRAPH_AMP_SYSTEM_PROMPT" > "$HOME/.sourcegraph-amp-module/SYSTEM_PROMPT.md"
echo "System prompt saved to $HOME/.sourcegraph-amp-module/SYSTEM_PROMPT.md"
printf "Node.js version: %s\n" "$(node --version)"
printf "npm version: %s\n" "$(npm --version)"
NPM_GLOBAL_PREFIX="${HOME}/.npm-global"
if [ ! -d "$NPM_GLOBAL_PREFIX" ]; then
mkdir -p "$NPM_GLOBAL_PREFIX"
fi
npm config set prefix "$NPM_GLOBAL_PREFIX"
export PATH="$NPM_GLOBAL_PREFIX/bin:$PATH"
if [ -n "$ARG_AMP_VERSION" ]; then
npm install -g "@sourcegraph/amp@$ARG_AMP_VERSION"
else
echo "No system prompt provided for Sourcegraph AMP."
npm install -g "@sourcegraph/amp"
fi
if ! grep -q 'export PATH="$HOME/.npm-global/bin:$PATH"' "$HOME/.bashrc"; then
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> "$HOME/.bashrc"
fi
}
install_amp_official() {
printf "%s Installing Amp using official installer\n" "${BOLD}"
if [ -n "$ARG_AMP_VERSION" ]; then
export AMP_VERSION="$ARG_AMP_VERSION"
printf "Installing Amp version: %s\n" "$AMP_VERSION"
fi
if curl -fsSL https://ampcode.com/install.sh | bash; then
export PATH="$HOME/.local/bin:$HOME/.amp/bin:$PATH"
if ! grep -q 'export PATH="$HOME/.local/bin:$PATH"' "$HOME/.bashrc"; then
echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc"
fi
else
printf "${YELLOW}Warning: Official installer failed. Installation skipped.${NC}\n"
return 1
fi
}
function install_amp() {
if [ "${ARG_INSTALL_AMP}" = "true" ]; then
if [ "${ARG_INSTALL_VIA_NPM}" = "true" ]; then
install_amp_npm || {
printf "${YELLOW}Amp installation via npm failed.${NC}\n"
return 0
}
else
install_amp_official || {
printf "${YELLOW}Amp installation via official installer failed.${NC}\n"
return 0
}
fi
if command_exists amp; then
printf "%s${GREEN}Successfully installed Sourcegraph Amp CLI. Version: %s${NC}\n" "${BOLD}" "$(amp --version)"
fi
else
printf "Skipping Sourcegraph Amp CLI installation (install_amp=false)\n"
fi
}
function setup_instruction_prompt() {
if [ -n "${ARG_AMP_INSTRUCTION_PROMPT:-}" ]; then
echo "Setting AMP instruction prompt..."
mkdir -p "$HOME/.config"
echo "$ARG_AMP_INSTRUCTION_PROMPT" > "$HOME/.config/AGENTS.md"
echo "Instruction prompt saved to $HOME/.config/AGENTS.md"
else
echo "No instruction prompt provided for Sourcegraph AMP."
fi
}
@@ -86,11 +126,17 @@ function configure_amp_settings() {
fi
echo "Writing AMP configuration to $SETTINGS_PATH"
printf '%s\n' "$ARG_AMP_CONFIG" > "$SETTINGS_PATH"
UPDATED_CONFIG=$(echo "$ARG_AMP_CONFIG" | jq --arg token "$CODER_AGENT_TOKEN" --arg url "$CODER_AGENT_URL" \
".[\"amp.mcpServers\"].coder.env += {
\"CODER_AGENT_TOKEN\": \"$CODER_AGENT_TOKEN\",
\"CODER_AGENT_URL\": \"$CODER_AGENT_URL\"
}")
printf "UPDATED_CONFIG: %s\n" "$UPDATED_CONFIG"
printf '%s\n' "$UPDATED_CONFIG" > "$SETTINGS_PATH"
echo "AMP configuration complete"
}
install_sourcegraph_amp
setup_system_prompt
install_amp
setup_instruction_prompt
configure_amp_settings
@@ -1,16 +1,18 @@
#!/bin/bash
set -euo pipefail
# Load user environment
# shellcheck source=/dev/null
source "$HOME/.bashrc"
# shellcheck source=/dev/null
if [ -f "$HOME/.nvm/nvm.sh" ]; then
source "$HOME"/.nvm/nvm.sh
else
export PATH="$HOME/.npm-global/bin:$PATH"
if [ -f "$HOME/.bashrc" ]; then
source "$HOME/.bashrc"
fi
if [ -f "$HOME/.nvm/nvm.sh" ]; then
source "$HOME/.nvm/nvm.sh"
fi
set -euo pipefail
export PATH="$HOME/.local/bin:$HOME/.amp/bin:$HOME/.npm-global/bin:$PATH"
function ensure_command() {
command -v "$1" &> /dev/null || {
echo "Error: '$1' not found." >&2
@@ -18,10 +20,22 @@ function ensure_command() {
}
}
ARG_AMP_START_DIRECTORY=${ARG_AMP_START_DIRECTORY:-"$HOME"}
ARG_AMP_API_KEY=${ARG_AMP_API_KEY:-}
ARG_AMP_TASK_PROMPT=$(echo -n "${ARG_AMP_TASK_PROMPT:-}" | base64 -d)
ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true}
echo "--------------------------------"
printf "Workspace: %s\n" "$ARG_AMP_START_DIRECTORY"
printf "Task Prompt: %s\n" "$ARG_AMP_TASK_PROMPT"
printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS"
printf "ARG_MODE: %s\n" "$ARG_MODE"
echo "--------------------------------"
ensure_command amp
echo "AMP version: $(amp --version)"
dir="$SOURCEGRAPH_AMP_START_DIRECTORY"
dir="$ARG_AMP_START_DIRECTORY"
if [[ -d "$dir" ]]; then
echo "Using existing directory: $dir"
else
@@ -30,20 +44,30 @@ else
fi
cd "$dir"
if [ -n "$SOURCEGRAPH_AMP_API_KEY" ]; then
printf "sourcegraph_amp_api_key provided !\n"
export AMP_API_KEY=$SOURCEGRAPH_AMP_API_KEY
if [ -n "$ARG_AMP_API_KEY" ]; then
printf "amp_api_key provided !\n"
export AMP_API_KEY=$ARG_AMP_API_KEY
else
printf "sourcegraph_amp_api_key not provided\n"
printf "amp_api_key not provided\n"
fi
if [ -n "${SOURCEGRAPH_AMP_TASK_PROMPT:-}" ]; then
printf "sourcegraph amp task prompt provided : $SOURCEGRAPH_AMP_TASK_PROMPT"
PROMPT="Every step of the way, report tasks to Coder with proper descriptions and statuses. Your task at hand: $SOURCEGRAPH_AMP_TASK_PROMPT"
ARGS=()
if [ -n "$ARG_MODE" ]; then
printf "Running agent in: %s mode" "$ARG_MODE"
ARGS+=(--mode "$ARG_MODE")
fi
if [ -n "$ARG_AMP_TASK_PROMPT" ]; then
if [ "$ARG_REPORT_TASKS" == "true" ]; then
printf "amp task prompt provided : %s" "$ARG_AMP_TASK_PROMPT\n"
PROMPT="Every step of the way, report your progress using coder_report_task tool with proper summary and statuses. Your task at hand: $ARG_AMP_TASK_PROMPT"
else
PROMPT="$ARG_AMP_TASK_PROMPT"
fi
# Pipe the prompt into amp, which will be run inside agentapi
agentapi server --term-width=67 --term-height=1190 -- bash -c "echo \"$PROMPT\" | amp"
agentapi server --type amp --term-width=67 --term-height=1190 -- bash -c "echo \"$PROMPT\" | amp" "${ARGS[@]}"
else
printf "No task prompt given.\n"
agentapi server --term-width=67 --term-height=1190 -- amp
agentapi server --type amp --term-width=67 --term-height=1190 -- amp "${ARGS[@]}"
fi
@@ -1,7 +1,8 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
source = "coder/coder"
version = ">= 2.13"
}
docker = {
source = "kreuzwerker/docker"
@@ -12,22 +13,32 @@ terraform {
# This template requires a valid Docker socket
# However, you can reference our Kubernetes/VM
# example templates and adapt the Claude Code module
#
# see: https://registry.coder.com/templates
#
# see: https://registry.coder.com/templates
provider "docker" {}
# A `coder_ai_task` resource enables Tasks and associates
# the task with the coder_app that will act as an AI agent.
resource "coder_ai_task" "task" {
count = data.coder_workspace.me.start_count
app_id = module.claude-code[count.index].task_app_id
}
# You can read the task prompt from the `coder_task` data source.
data "coder_task" "me" {}
# The Claude Code module does the automatic task reporting
# Other agent modules: https://registry.coder.com/modules?search=agent
# Or use a custom agent:
# Or use a custom agent:
module "claude-code" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/claude-code/coder"
version = "3.0.0"
version = "4.0.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/projects"
order = 999
claude_api_key = ""
ai_prompt = data.coder_parameter.ai_prompt.value
ai_prompt = data.coder_task.me.prompt
system_prompt = data.coder_parameter.system_prompt.value
model = "sonnet"
permission_mode = "plan"
@@ -51,13 +62,13 @@ data "coder_workspace_preset" "default" {
(servers, dev watchers, GUI apps).
- Built-in tools - use for everything else:
(file operations, git commands, builds & installs, one-off shell commands)
Remember this decision rule:
- Stays running? desktop-commander
- Finishes immediately? built-in tools
-- Context --
There is an existing app and tmux dev server running on port 8000. Be sure to read it's CLAUDE.md (./realworld-django-rest-framework-angular/CLAUDE.md) to learn more about it.
There is an existing app and tmux dev server running on port 8000. Be sure to read it's CLAUDE.md (./realworld-django-rest-framework-angular/CLAUDE.md) to learn more about it.
Since this app is for demo purposes and the user is previewing the homepage and subsequent pages, aim to make the first visual change/prototype very quickly so the user can preview it, then focus on backend or logic which can be a more involved, long-running architecture plan.
@@ -107,7 +118,7 @@ data "coder_workspace_preset" "default" {
# Pre-builds is a Coder Premium
# feature to speed up workspace creation
#
#
# see https://coder.com/docs/admin/templates/extending-templates/prebuilt-workspaces
# prebuilds {
# instances = 1
@@ -126,13 +137,6 @@ data "coder_parameter" "system_prompt" {
description = "System prompt for the agent with generalized instructions"
mutable = false
}
data "coder_parameter" "ai_prompt" {
type = "string"
name = "AI Prompt"
default = ""
description = "Write a prompt for Claude Code"
mutable = true
}
data "coder_parameter" "setup_script" {
name = "setup_script"
display_name = "Setup Script"
@@ -373,4 +377,4 @@ resource "docker_container" "workspace" {
label = "coder.workspace_name"
value = data.coder_workspace.me.name
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 976 KiB

After

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 KiB

After

Width:  |  Height:  |  Size: 191 KiB

+1 -1
View File
@@ -16,7 +16,7 @@ The AgentAPI module is a building block for modules that need to run an AgentAPI
```tf
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "1.2.0"
version = "2.0.0"
agent_id = var.agent_id
web_app_slug = local.app_slug
+3 -5
View File
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.7"
version = ">= 2.12"
}
}
}
@@ -239,8 +239,6 @@ resource "coder_app" "agentapi_cli" {
group = var.cli_app_group
}
resource "coder_ai_task" "agentapi" {
sidebar_app {
id = coder_app.agentapi_web.id
}
output "task_app_id" {
value = coder_app.agentapi_web.id
}
+41 -15
View File
@@ -4,11 +4,35 @@ import {
removeContainer,
runContainer,
runTerraformApply,
TerraformState,
writeFileContainer,
} from "~test";
import path from "path";
import { expect } from "bun:test";
/**
* Extracts all coder_env resources from Terraform state and returns them as
* a Record of environment variable names to values.
*/
export const extractCoderEnvVars = (
state: TerraformState,
): Record<string, string> => {
const envVars: Record<string, string> = {};
for (const resource of state.resources) {
if (resource.type === "coder_env" && resource.instances.length > 0) {
const instance = resource.instances[0].attributes;
const name = instance.name as string;
const value = instance.value as string;
if (name && value) {
envVars[name] = value;
}
}
}
return envVars;
};
export const setupContainer = async ({
moduleDir,
image,
@@ -23,10 +47,12 @@ export const setupContainer = async ({
...vars,
});
const coderScript = findResourceInstance(state, "coder_script");
const coderEnvVars = extractCoderEnvVars(state);
const id = await runContainer(image ?? "codercom/enterprise-node:latest");
return {
id,
coderScript,
coderEnvVars,
cleanup: async () => {
if (
process.env["DEBUG"] === "true" ||
@@ -79,9 +105,11 @@ interface SetupProps {
agentapiMockScript?: string;
}
export const setup = async (props: SetupProps): Promise<{ id: string }> => {
export const setup = async (
props: SetupProps,
): Promise<{ id: string; coderEnvVars: Record<string, string> }> => {
const projectDir = props.projectDir ?? "/home/coder/project";
const { id, coderScript, cleanup } = await setupContainer({
const { id, coderScript, coderEnvVars, cleanup } = await setupContainer({
moduleDir: props.moduleDir,
vars: props.moduleVariables,
});
@@ -101,7 +129,7 @@ export const setup = async (props: SetupProps): Promise<{ id: string }> => {
filePath: "/home/coder/script.sh",
content: coderScript.script,
});
return { id };
return { id, coderEnvVars };
};
export const expectAgentAPIStarted = async (
@@ -125,18 +153,16 @@ export const execModuleScript = async (
id: string,
env?: Record<string, string>,
) => {
const envArgs = Object.entries(env ?? {})
.map(([key, value]) => ["--env", `${key}=${value}`])
.flat();
const resp = await execContainer(
id,
[
"bash",
"-c",
`set -o errexit; set -o pipefail; cd /home/coder && ./script.sh 2>&1 | tee /home/coder/script.log`,
],
envArgs,
);
const envArgs = env
? Object.entries(env)
.map(([key, value]) => `export ${key}="${value.replace(/"/g, '\\"')}"`)
.join(" && ") + " && "
: "";
const resp = await execContainer(id, [
"bash",
"-c",
`${envArgs}set -o errexit; set -o pipefail; cd /home/coder && ./script.sh 2>&1 | tee /home/coder/script.log`,
]);
if (resp.exitCode !== 0) {
console.log(resp.stdout);
console.log(resp.stderr);
+56 -230
View File
@@ -8,76 +8,58 @@ tags: [agent, ai, aider]
# Aider
Run [Aider](https://aider.chat) AI pair programming in your workspace. This module installs Aider and provides a persistent session using screen or tmux.
Run [Aider](https://aider.chat) AI pair programming in your workspace. This module installs Aider with AgentAPI for seamless Coder Tasks Support.
```tf
module "aider" {
source = "registry.coder.com/coder/aider/coder"
version = "1.1.2"
agent_id = coder_agent.example.id
}
```
## Features
- **Interactive Parameter Selection**: Choose your AI provider, model, and configuration options when creating the workspace
- **Multiple AI Providers**: Supports Anthropic (Claude), OpenAI, DeepSeek, GROQ, and OpenRouter
- **Persistent Sessions**: Uses screen (default) or tmux to keep Aider running in the background
- **Optional Dependencies**: Install Playwright for web page scraping and PortAudio for voice coding
- **Project Integration**: Works with any project directory, including Git repositories
- **Browser UI**: Use Aider in your browser with a modern web interface instead of the terminal
- **Non-Interactive Mode**: Automatically processes tasks when provided via the `task_prompt` variable
## Module Parameters
> [!NOTE]
> The `use_screen` and `use_tmux` parameters cannot both be enabled at the same time. By default, `use_screen` is set to `true` and `use_tmux` is set to `false`.
## Usage Examples
### Basic setup with API key
```tf
variable "anthropic_api_key" {
variable "api_key" {
type = string
description = "Anthropic API key"
description = "API key"
sensitive = true
}
module "aider" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/aider/coder"
version = "1.1.2"
agent_id = coder_agent.example.id
ai_api_key = var.anthropic_api_key
}
```
This basic setup will:
- Install Aider in the workspace
- Create a persistent screen session named "aider"
- Configure Aider to use Anthropic Claude 3.7 Sonnet model
- Enable task reporting (configures Aider to report tasks to Coder MCP)
### Using OpenAI with tmux
```tf
variable "openai_api_key" {
type = string
description = "OpenAI API key"
sensitive = true
}
module "aider" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/aider/coder"
version = "1.1.2"
agent_id = coder_agent.example.id
use_tmux = true
ai_provider = "openai"
ai_model = "4o" # Uses Aider's built-in alias for gpt-4o
ai_api_key = var.openai_api_key
version = "2.0.1"
agent_id = coder_agent.main.id
api_key = var.api_key
ai_provider = "google"
model = "gemini"
}
```
## Prerequisites
- pipx is automatically installed if not already available
## Usage Example
```tf
data "coder_parameter" "ai_prompt" {
name = "AI Prompt"
description = "Write an initial prompt for Aider to work on."
type = "string"
default = ""
mutable = true
}
variable "gemini_api_key" {
type = string
description = "Gemini API key"
sensitive = true
}
module "aider" {
source = "registry.coder.com/coder/aider/coder"
version = "2.0.1"
agent_id = coder_agent.main.id
api_key = var.gemini_api_key
install_aider = true
workdir = "/home/coder"
ai_provider = "google"
model = "gemini"
install_agentapi = true
ai_prompt = data.coder_parameter.ai_prompt.value
system_prompt = "..."
}
```
@@ -93,174 +75,16 @@ variable "custom_api_key" {
module "aider" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/aider/coder"
version = "1.1.2"
agent_id = coder_agent.example.id
version = "2.0.1"
agent_id = coder_agent.main.id
workdir = "/home/coder"
ai_provider = "custom"
custom_env_var_name = "MY_CUSTOM_API_KEY"
ai_model = "custom-model"
ai_api_key = var.custom_api_key
model = "custom-model"
api_key = var.custom_api_key
}
```
### Adding Custom Extensions (Experimental)
You can extend Aider's capabilities by adding custom extensions:
```tf
module "aider" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/aider/coder"
version = "1.1.2"
agent_id = coder_agent.example.id
ai_api_key = var.anthropic_api_key
experiment_pre_install_script = <<-EOT
pip install some-custom-dependency
EOT
experiment_additional_extensions = <<-EOT
custom-extension:
args: []
cmd: custom-extension-command
description: A custom extension for Aider
enabled: true
envs: {}
name: custom-extension
timeout: 300
type: stdio
EOT
}
```
Note: The indentation in the heredoc is preserved, so you can write the YAML naturally.
## Task Reporting (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.
Your workspace must have either `screen` or `tmux` installed to use this.
Task reporting is **enabled by default** in this module, allowing you to:
- Send an initial prompt to Aider during workspace creation
- Monitor task progress in the Coder UI
- Use the `coder_parameter` resource to collect prompts from users
### Setting up Task Reporting
To use task reporting effectively:
1. Add the Coder Login module to your template
2. Configure the necessary variables to pass the task prompt
3. Optionally add a coder_parameter to collect prompts from users
Here's a complete example:
```tf
module "coder-login" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/modules/coder-login/coder"
version = "1.0.15"
agent_id = coder_agent.example.id
}
variable "anthropic_api_key" {
type = string
description = "Anthropic API key"
sensitive = true
}
data "coder_parameter" "ai_prompt" {
type = "string"
name = "AI Prompt"
default = ""
description = "Write a prompt for Aider"
mutable = true
ephemeral = true
}
module "aider" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/aider/coder"
version = "1.1.2"
agent_id = coder_agent.example.id
ai_api_key = var.anthropic_api_key
task_prompt = data.coder_parameter.ai_prompt.value
# Optionally customize the system prompt
system_prompt = <<-EOT
You are a helpful Coding assistant. Aim to autonomously investigate
and solve issues the user gives you and test your work, whenever possible.
Avoid shortcuts like mocking tests. When you get stuck, you can ask the user
but opt for autonomy.
YOU MUST REPORT ALL TASKS TO CODER.
When reporting tasks, you MUST follow these EXACT instructions:
- IMMEDIATELY report status after receiving ANY user message.
- Be granular. If you are investigating with multiple steps, report each step to coder.
Task state MUST be one of the following:
- Use "state": "working" when actively processing WITHOUT needing additional user input.
- Use "state": "complete" only when finished with a task.
- Use "state": "failure" when you need ANY user input, lack sufficient details, or encounter blockers.
Task summaries MUST:
- Include specifics about what you're doing.
- Include clear and actionable steps for the user.
- Be less than 160 characters in length.
EOT
}
```
When a task prompt is provided via the `task_prompt` variable, the module automatically:
1. Combines the system prompt with the task prompt into a single message in the format:
```
SYSTEM PROMPT:
[system_prompt content]
This is your current task: [task_prompt]
```
2. Executes the task during workspace creation using the `--message` and `--yes-always` flags
3. Logs task output to `$HOME/.aider.log` for reference
If you want to disable task reporting, set `experiment_report_tasks = false` in your module configuration.
## Using Aider in Your Workspace
After the workspace starts, Aider will be installed and configured according to your parameters. A persistent session will automatically be started during workspace creation.
### Session Options
You can run Aider in three different ways:
1. **Direct Mode**: Aider starts directly in the specified folder when you click the app button
- Simple setup without persistent context
- Suitable for quick coding sessions
2. **Screen Mode** (Default): Run Aider in a screen session that persists across connections
- Session name: "aider" (or configured via `session_name`)
3. **Tmux Mode**: Run Aider in a tmux session instead of screen
- Set `use_tmux = true` to enable
- Session name: "aider" (or configured via `session_name`)
- Configures tmux with mouse support for shared sessions
Persistent sessions (screen/tmux) allow you to:
- Disconnect and reconnect without losing context
- Run Aider in the background while doing other work
- Switch between terminal and browser interfaces
### Available AI Providers and Models
Aider supports various providers and models, and this module integrates directly with Aider's built-in model aliases:
@@ -280,10 +104,12 @@ For a complete and up-to-date list of supported aliases and models, please refer
## Troubleshooting
If you encounter issues:
- If `aider` is not found, ensure `install_aider = true` and your API key is valid
- Logs are written under `/home/coder/.aider-module/` (`install.log`, `agentapi-start.log`) for debugging
- If AgentAPI fails to start, verify that your container has network access and executable permissions for the scripts
1. **Screen/Tmux issues**: If you can't reconnect to your session, check if the session exists with `screen -list` or `tmux list-sessions`
2. **API key issues**: Ensure you've entered the correct API key for your selected provider
3. **Browser mode issues**: If the browser interface doesn't open, check that you're accessing it from a machine that can reach your Coder workspace
## References
For more information on using Aider, see the [Aider documentation](https://aider.chat/docs/).
- [Aider Documentation](https://aider.chat/docs)
- [AgentAPI Documentation](https://github.com/coder/agentapi)
- [Coder AI Agents Guide](https://coder.com/docs/tutorials/ai-agents)
+114 -83
View File
@@ -1,107 +1,138 @@
import { describe, expect, it } from "bun:test";
import {
findResourceInstance,
runTerraformApply,
runTerraformInit,
testRequiredVariables,
} from "~test";
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";
describe("aider", async () => {
await runTerraformInit(import.meta.dir);
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);
}
}
});
testRequiredVariables(import.meta.dir, {
agent_id: "foo",
interface SetupProps {
skipAgentAPIMock?: boolean;
skipAiderMock?: 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_aider: props?.skipAiderMock ? "true" : "false",
install_agentapi: props?.skipAgentAPIMock ? "true" : "false",
aider_model: "test-model",
...props?.moduleVariables,
},
registerCleanup,
projectDir,
skipAgentAPIMock: props?.skipAgentAPIMock,
agentapiMockScript: props?.agentapiMockScript,
});
it("configures task prompt correctly", async () => {
const testPrompt = "Add a hello world function";
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
task_prompt: testPrompt,
// Place the Aider mock CLI binary inside the container
if (!props?.skipAiderMock) {
await writeExecutable({
containerId: id,
filePath: "/usr/bin/aider",
content: await loadTestFile(`${import.meta.dir}`, "aider-mock.sh"),
});
}
const instance = findResourceInstance(state, "coder_script");
expect(instance.script).toContain(
`This is your current task: ${testPrompt}`,
);
expect(instance.script).toContain("aider --architect --yes-always");
return { id };
};
setDefaultTimeout(60 * 1000);
describe("Aider", async () => {
beforeAll(async () => {
await runTerraformInit(import.meta.dir);
});
it("handles custom system prompt", async () => {
const customPrompt = "Report all tasks with state: working";
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
system_prompt: customPrompt,
test("happy-path", async () => {
const { id } = await setup({
moduleVariables: {
model: "gemini",
},
});
const instance = findResourceInstance(state, "coder_script");
expect(instance.script).toContain(customPrompt);
await execModuleScript(id);
await expectAgentAPIStarted(id);
});
it("handles pre and post install scripts", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
experiment_pre_install_script: "echo 'Pre-install script executed'",
experiment_post_install_script: "echo 'Post-install script executed'",
test("api-key", async () => {
const apiKey = "test-api-key-123";
const { id } = await setup({
moduleVariables: {
api_key: apiKey,
model: "gemini",
},
});
const instance = findResourceInstance(state, "coder_script");
expect(instance.script).toContain("Running pre-install script");
expect(instance.script).toContain("Running post-install script");
expect(instance.script).toContain("base64 -d > /tmp/pre_install.sh");
expect(instance.script).toContain("base64 -d > /tmp/post_install.sh");
await execModuleScript(id);
const resp = await readFileContainer(
id,
"/home/coder/.aider-module/agentapi-start.log",
);
expect(resp).toContain("API key provided!");
});
it("validates that use_screen and use_tmux cannot both be true", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
use_screen: true,
use_tmux: true,
test("custom-folder", async () => {
const workdir = "/tmp/aider-test";
const { id } = await setup({
moduleVariables: {
workdir,
model: "gemini",
},
});
const instance = findResourceInstance(state, "coder_script");
expect(instance.script).toContain(
"Error: Both use_screen and use_tmux cannot be enabled at the same time",
await execModuleScript(id);
const resp = await readFileContainer(
id,
"/home/coder/.aider-module/install.log",
);
expect(instance.script).toContain("exit 1");
expect(resp).toContain(workdir);
});
it("configures Aider with known provider and model", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
ai_provider: "anthropic",
ai_model: "sonnet",
ai_api_key: "test-anthropic-key",
test("pre-post-install-scripts", async () => {
const { id } = await setup({
moduleVariables: {
pre_install_script: "#!/bin/bash\necho 'pre-install-script'",
post_install_script: "#!/bin/bash\necho 'post-install-script'",
model: "gemini",
},
});
const instance = findResourceInstance(state, "coder_script");
expect(instance.script).toContain(
'export ANTHROPIC_API_KEY=\\"test-anthropic-key\\"',
await execModuleScript(id);
const preLog = await readFileContainer(
id,
"/home/coder/.aider-module/pre_install.log",
);
expect(instance.script).toContain("--model sonnet");
expect(instance.script).toContain(
"Starting Aider using anthropic provider and model: sonnet",
);
});
it("handles custom provider with custom env var and API key", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
ai_provider: "custom",
custom_env_var_name: "MY_CUSTOM_API_KEY",
ai_model: "custom-model",
ai_api_key: "test-custom-key",
});
const instance = findResourceInstance(state, "coder_script");
expect(instance.script).toContain(
'export MY_CUSTOM_API_KEY=\\"test-custom-key\\"',
);
expect(instance.script).toContain("--model custom-model");
expect(instance.script).toContain(
"Starting Aider using custom provider and model: custom-model",
expect(preLog).toContain("pre-install-script");
const postLog = await readFileContainer(
id,
"/home/coder/.aider-module/post_install.log",
);
expect(postLog).toContain("post-install-script");
});
});
+165 -394
View File
@@ -36,87 +36,84 @@ variable "icon" {
default = "/icon/aider.svg"
}
variable "folder" {
variable "workdir" {
type = string
description = "The folder to run Aider in."
default = "/home/coder"
}
variable "report_tasks" {
type = bool
description = "Whether to enable task reporting to Coder UI via AgentAPI"
default = false
}
variable "subdomain" {
type = bool
description = "Whether to use a subdomain for AgentAPI."
default = false
}
variable "cli_app" {
type = bool
description = "Whether to create a CLI app for Aider"
default = false
}
variable "web_app_display_name" {
type = string
description = "Display name for the web app"
default = "Aider"
}
variable "cli_app_display_name" {
type = string
description = "Display name for the CLI app"
default = "Aider CLI"
}
variable "pre_install_script" {
type = string
description = "Custom script to run before installing Aider."
default = null
}
variable "post_install_script" {
type = string
description = "Custom script to run after installing Aider."
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.10.0"
}
variable "ai_prompt" {
type = string
description = "Initial task prompt for Aider."
default = ""
}
# ---------------------------------------------
variable "install_aider" {
type = bool
description = "Whether to install Aider."
default = true
}
variable "aider_version" {
type = string
description = "The version of Aider to install."
default = "latest"
}
variable "use_screen" {
type = bool
description = "Whether to use screen for running Aider in the background"
default = true
}
variable "use_tmux" {
type = bool
description = "Whether to use tmux instead of screen for running Aider in the background"
default = false
}
variable "session_name" {
type = string
description = "Name for the persistent session (screen or tmux)"
default = "aider"
}
variable "experiment_report_tasks" {
type = bool
description = "Whether to enable task reporting."
default = true
}
variable "system_prompt" {
type = string
description = "System prompt for instructing Aider on task reporting and behavior"
default = <<-EOT
You are a helpful Coding assistant. Aim to autonomously investigate
and solve issues the user gives you and test your work, whenever possible.
Avoid shortcuts like mocking tests. When you get stuck, you can ask the user
but opt for autonomy.
YOU MUST REPORT ALL TASKS TO CODER.
When reporting tasks, you MUST follow these EXACT instructions:
- IMMEDIATELY report status after receiving ANY user message.
- Be granular. If you are investigating with multiple steps, report each step to coder.
Task state MUST be one of the following:
- Use "state": "working" when actively processing WITHOUT needing additional user input.
- Use "state": "complete" only when finished with a task.
- Use "state": "failure" when you need ANY user input, lack sufficient details, or encounter blockers.
Task summaries MUST:
- Include specifics about what you're doing.
- Include clear and actionable steps for the user.
- Be less than 160 characters in length.
EOT
}
variable "task_prompt" {
type = string
description = "Task prompt to use with Aider"
default = ""
}
variable "experiment_pre_install_script" {
type = string
description = "Custom script to run before installing Aider."
default = null
}
variable "experiment_post_install_script" {
type = string
description = "Custom script to run after installing Aider."
default = null
default = "You are a helpful coding assistant that helps developers write, debug, and understand code. Provide clear explanations, follow best practices, and help solve coding problems efficiently."
}
variable "experiment_additional_extensions" {
@@ -128,20 +125,19 @@ variable "experiment_additional_extensions" {
variable "ai_provider" {
type = string
description = "AI provider to use with Aider (openai, anthropic, azure, google, etc.)"
default = "anthropic"
default = "google"
validation {
condition = contains(["openai", "anthropic", "azure", "google", "cohere", "mistral", "ollama", "custom"], var.ai_provider)
error_message = "ai_provider must be one of: openai, anthropic, azure, google, cohere, mistral, ollama, custom"
error_message = "provider must be one of: openai, anthropic, azure, google, cohere, mistral, ollama, custom"
}
}
variable "ai_model" {
variable "model" {
type = string
description = "AI model to use with Aider. Can use Aider's built-in aliases like '4o' (gpt-4o), 'sonnet' (claude-3-7-sonnet), 'opus' (claude-3-opus), etc."
default = "sonnet"
}
variable "ai_api_key" {
variable "api_key" {
type = string
description = "API key for the selected AI provider. This will be set as the appropriate environment variable based on the provider."
default = ""
@@ -154,55 +150,66 @@ variable "custom_env_var_name" {
default = ""
}
variable "base_aider_config" {
type = string
description = <<-EOT
Base Aider configuration in yaml format. Will be stored in .aider.conf.yml file.
options include:
read:
- CONVENTIONS.md
- anotherfile.txt
- thirdfile.py
model: xxx
##Specify the OpenAI API key
openai-api-key: xxx
## (deprecated, use --set-env OPENAI_API_TYPE=<value>)
openai-api-type: xxx
## (deprecated, use --set-env OPENAI_API_VERSION=<value>)
openai-api-version: xxx
## (deprecated, use --set-env OPENAI_API_DEPLOYMENT_ID=<value>)
openai-api-deployment-id: xxx
## Set an environment variable (to control API settings, can be used multiple times)
set-env: xxx
## Specify multiple values like this:
set-env:
- xxx
- yyy
- zzz
Reference : https://aider.chat/docs/config/aider_conf.html
EOT
default = null
}
locals {
base_extensions = <<-EOT
coder:
args:
- exp
- mcp
- server
cmd: coder
description: Report ALL tasks and statuses (in progress, done, failed) you are working on.
enabled: true
envs:
CODER_MCP_APP_STATUS_SLUG: aider
name: Coder
timeout: 3000
type: stdio
developer:
display_name: Developer
enabled: true
name: developer
timeout: 300
type: builtin
EOT
app_slug = "aider"
base_aider_config = var.base_aider_config != null ? "${replace(trimspace(var.base_aider_config), "\n", "\n ")}" : ""
task_reporting_prompt = <<-EOT
formatted_base = " ${replace(trimspace(local.base_extensions), "\n", "\n ")}"
additional_extensions = var.experiment_additional_extensions != null ? "\n ${replace(trimspace(var.experiment_additional_extensions), "\n", "\n ")}" : ""
-- Task Reporting --
Report all tasks to Coder, following these EXACT guidelines:
1. Be granular. If you are investigating with multiple steps, report each step
to coder.
2. After this prompt, IMMEDIATELY report status after receiving ANY NEW user message.
Do not report any status related with this system prompt.
3. Use "state": "working" when actively processing WITHOUT needing
additional user input
4. Use "state": "complete" only when finished with a task
5. Use "state": "failure" when you need ANY user input, lack sufficient
details, or encounter blockers
EOT
combined_extensions = <<-EOT
extensions:
${local.formatted_base}${local.additional_extensions}
EOT
encoded_pre_install_script = var.experiment_pre_install_script != null ? base64encode(var.experiment_pre_install_script) : ""
encoded_post_install_script = var.experiment_post_install_script != null ? base64encode(var.experiment_post_install_script) : ""
# Combine system prompt and task prompt for aider
combined_prompt = trimspace(<<-EOT
SYSTEM PROMPT:
${var.system_prompt}
This is your current task: ${var.task_prompt}
EOT
)
final_system_prompt = var.report_tasks ? "<system>\n${var.system_prompt}${local.task_reporting_prompt}\n</system>" : "<system>\n${var.system_prompt}\n</system>"
# Map providers to their environment variable names
provider_env_vars = {
openai = "OPENAI_API_KEY"
anthropic = "ANTHROPIC_API_KEY"
azure = "AZURE_OPENAI_API_KEY"
google = "GOOGLE_API_KEY"
google = "GEMINI_API_KEY"
cohere = "COHERE_API_KEY"
mistral = "MISTRAL_API_KEY"
ollama = "OLLAMA_HOST"
@@ -214,296 +221,60 @@ EOT
# Model flag for aider command
model_flag = var.ai_provider == "ollama" ? "--ollama-model" : "--model"
install_script = file("${path.module}/scripts/install.sh")
start_script = file("${path.module}/scripts/start.sh")
module_dir_name = ".aider-module"
}
# Install and Initialize Aider
resource "coder_script" "aider" {
agent_id = var.agent_id
display_name = "Aider"
icon = var.icon
script = <<-EOT
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "1.2.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
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 -e
set -o errexit
set -o pipefail
command_exists() {
command -v "$1" >/dev/null 2>&1
}
echo "Setting up Aider AI pair programming..."
if [ "${var.use_screen}" = "true" ] && [ "${var.use_tmux}" = "true" ]; then
echo "Error: Both use_screen and use_tmux cannot be enabled at the same time."
exit 1
fi
mkdir -p "${var.folder}"
if [ "$(uname)" = "Linux" ]; then
echo "Checking dependencies for Linux..."
if [ "${var.use_tmux}" = "true" ]; then
if ! command_exists tmux; then
echo "Installing tmux for persistent sessions..."
if command -v apt-get >/dev/null 2>&1; then
if command -v sudo >/dev/null 2>&1; then
sudo apt-get update -qq
sudo apt-get install -y -qq tmux
else
apt-get update -qq || echo "Warning: Cannot update package lists without sudo privileges"
apt-get install -y -qq tmux || echo "Warning: Cannot install tmux without sudo privileges"
fi
elif command -v dnf >/dev/null 2>&1; then
if command -v sudo >/dev/null 2>&1; then
sudo dnf install -y -q tmux
else
dnf install -y -q tmux || echo "Warning: Cannot install tmux without sudo privileges"
fi
else
echo "Warning: Unable to install tmux on this system. Neither apt-get nor dnf found."
fi
else
echo "tmux is already installed, skipping installation."
fi
elif [ "${var.use_screen}" = "true" ]; then
if ! command_exists screen; then
echo "Installing screen for persistent sessions..."
if command -v apt-get >/dev/null 2>&1; then
if command -v sudo >/dev/null 2>&1; then
sudo apt-get update -qq
sudo apt-get install -y -qq screen
else
apt-get update -qq || echo "Warning: Cannot update package lists without sudo privileges"
apt-get install -y -qq screen || echo "Warning: Cannot install screen without sudo privileges"
fi
elif command -v dnf >/dev/null 2>&1; then
if command -v sudo >/dev/null 2>&1; then
sudo dnf install -y -q screen
else
dnf install -y -q screen || echo "Warning: Cannot install screen without sudo privileges"
fi
else
echo "Warning: Unable to install screen on this system. Neither apt-get nor dnf found."
fi
else
echo "screen is already installed, skipping installation."
fi
fi
else
echo "This module currently only supports Linux workspaces."
exit 1
fi
if [ -n "${local.encoded_pre_install_script}" ]; then
echo "Running pre-install script..."
echo "${local.encoded_pre_install_script}" | base64 -d > /tmp/pre_install.sh
chmod +x /tmp/pre_install.sh
/tmp/pre_install.sh
fi
if [ "${var.install_aider}" = "true" ]; then
echo "Installing Aider..."
if ! command_exists python3 || ! command_exists pip3; then
echo "Installing Python dependencies required for Aider..."
if command -v apt-get >/dev/null 2>&1; then
if command -v sudo >/dev/null 2>&1; then
sudo apt-get update -qq
sudo apt-get install -y -qq python3-pip python3-venv
else
apt-get update -qq || echo "Warning: Cannot update package lists without sudo privileges"
apt-get install -y -qq python3-pip python3-venv || echo "Warning: Cannot install Python packages without sudo privileges"
fi
elif command -v dnf >/dev/null 2>&1; then
if command -v sudo >/dev/null 2>&1; then
sudo dnf install -y -q python3-pip python3-virtualenv
else
dnf install -y -q python3-pip python3-virtualenv || echo "Warning: Cannot install Python packages without sudo privileges"
fi
else
echo "Warning: Unable to install Python on this system. Neither apt-get nor dnf found."
fi
else
echo "Python is already installed, skipping installation."
fi
if ! command_exists aider; then
curl -LsSf https://aider.chat/install.sh | sh
fi
if [ -f "$HOME/.bashrc" ]; then
if ! grep -q 'export PATH="$HOME/bin:$PATH"' "$HOME/.bashrc"; then
echo 'export PATH="$HOME/bin:$PATH"' >> "$HOME/.bashrc"
fi
fi
if [ -f "$HOME/.zshrc" ]; then
if ! grep -q 'export PATH="$HOME/bin:$PATH"' "$HOME/.zshrc"; then
echo 'export PATH="$HOME/bin:$PATH"' >> "$HOME/.zshrc"
fi
fi
fi
if [ -n "${local.encoded_post_install_script}" ]; then
echo "Running post-install script..."
echo "${local.encoded_post_install_script}" | base64 -d > /tmp/post_install.sh
chmod +x /tmp/post_install.sh
/tmp/post_install.sh
fi
if [ "${var.experiment_report_tasks}" = "true" ]; then
echo "Configuring Aider to report tasks via Coder MCP..."
mkdir -p "$HOME/.config/aider"
cat > "$HOME/.config/aider/config.yml" << EOL
${trimspace(local.combined_extensions)}
EOL
echo "Added Coder MCP extension to Aider config.yml"
fi
echo "Starting persistent Aider session..."
touch "$HOME/.aider.log"
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export PATH="$HOME/bin:$PATH"
if [ "${var.use_tmux}" = "true" ]; then
if [ -n "${var.task_prompt}" ]; then
echo "Running Aider with message in tmux session..."
# Configure tmux for shared sessions
if [ ! -f "$HOME/.tmux.conf" ]; then
echo "Creating ~/.tmux.conf with shared session settings..."
echo "set -g mouse on" > "$HOME/.tmux.conf"
fi
if ! grep -q "^set -g mouse on$" "$HOME/.tmux.conf"; then
echo "Adding 'set -g mouse on' to ~/.tmux.conf..."
echo "set -g mouse on" >> "$HOME/.tmux.conf"
fi
echo "Starting Aider using ${var.ai_provider} provider and model: ${var.ai_model}"
tmux new-session -d -s ${var.session_name} -c ${var.folder} "export ${local.env_var_name}=\"${var.ai_api_key}\"; aider --architect --yes-always ${local.model_flag} ${var.ai_model} --message \"${local.combined_prompt}\""
echo "Aider task started in tmux session '${var.session_name}'. Check the UI for progress."
else
# Configure tmux for shared sessions
if [ ! -f "$HOME/.tmux.conf" ]; then
echo "Creating ~/.tmux.conf with shared session settings..."
echo "set -g mouse on" > "$HOME/.tmux.conf"
fi
if ! grep -q "^set -g mouse on$" "$HOME/.tmux.conf"; then
echo "Adding 'set -g mouse on' to ~/.tmux.conf..."
echo "set -g mouse on" >> "$HOME/.tmux.conf"
fi
echo "Starting Aider using ${var.ai_provider} provider and model: ${var.ai_model}"
tmux new-session -d -s ${var.session_name} -c ${var.folder} "export ${local.env_var_name}=\"${var.ai_api_key}\"; aider --architect --yes-always ${local.model_flag} ${var.ai_model} --message \"${var.system_prompt}\""
echo "Tmux session '${var.session_name}' started. Access it by clicking the Aider button."
fi
else
if [ -n "${var.task_prompt}" ]; then
echo "Running Aider with message in screen session..."
if [ ! -f "$HOME/.screenrc" ]; then
echo "Creating ~/.screenrc and adding multiuser settings..."
echo -e "multiuser on\nacladd $(whoami)" > "$HOME/.screenrc"
fi
if ! grep -q "^multiuser on$" "$HOME/.screenrc"; then
echo "Adding 'multiuser on' to ~/.screenrc..."
echo "multiuser on" >> "$HOME/.screenrc"
fi
if ! grep -q "^acladd $(whoami)$" "$HOME/.screenrc"; then
echo "Adding 'acladd $(whoami)' to ~/.screenrc..."
echo "acladd $(whoami)" >> "$HOME/.screenrc"
fi
echo "Starting Aider using ${var.ai_provider} provider and model: ${var.ai_model}"
screen -U -dmS ${var.session_name} bash -c "
cd ${var.folder}
export PATH=\"$HOME/bin:$HOME/.local/bin:$PATH\"
export ${local.env_var_name}=\"${var.ai_api_key}\"
aider --architect --yes-always ${local.model_flag} ${var.ai_model} --message \"${local.combined_prompt}\"
/bin/bash
"
echo "Aider task started in screen session '${var.session_name}'. Check the UI for progress."
else
if [ ! -f "$HOME/.screenrc" ]; then
echo "Creating ~/.screenrc and adding multiuser settings..."
echo -e "multiuser on\nacladd $(whoami)" > "$HOME/.screenrc"
fi
if ! grep -q "^multiuser on$" "$HOME/.screenrc"; then
echo "Adding 'multiuser on' to ~/.screenrc..."
echo "multiuser on" >> "$HOME/.screenrc"
fi
if ! grep -q "^acladd $(whoami)$" "$HOME/.screenrc"; then
echo "Adding 'acladd $(whoami)' to ~/.screenrc..."
echo "acladd $(whoami)" >> "$HOME/.screenrc"
fi
echo "Starting Aider using ${var.ai_provider} provider and model: ${var.ai_model}"
screen -U -dmS ${var.session_name} bash -c "
cd ${var.folder}
export PATH=\"$HOME/bin:$HOME/.local/bin:$PATH\"
export ${local.env_var_name}=\"${var.ai_api_key}\"
aider --architect --yes-always ${local.model_flag} ${var.ai_model} --message \"${local.combined_prompt}\"
/bin/bash
"
echo "Screen session '${var.session_name}' started. Access it by clicking the Aider button."
fi
fi
echo "Aider setup complete!"
echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh
chmod +x /tmp/start.sh
ARG_WORKDIR='${var.workdir}' \
ARG_API_KEY='${base64encode(var.api_key)}' \
ARG_MODEL='${var.model}' \
ARG_PROVIDER='${var.ai_provider}' \
ARG_ENV_API_NAME_HOLDER='${local.env_var_name}' \
ARG_SYSTEM_PROMPT='${base64encode(local.final_system_prompt)}' \
ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \
/tmp/start.sh
EOT
run_on_start = true
}
# Aider CLI app
resource "coder_app" "aider_cli" {
agent_id = var.agent_id
slug = "aider"
display_name = "Aider"
icon = var.icon
command = <<-EOT
install_script = <<-EOT
#!/bin/bash
set -e
set -o errexit
set -o pipefail
export PATH="$HOME/bin:$HOME/.local/bin:$PATH"
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
if [ "${var.use_tmux}" = "true" ]; then
if tmux has-session -t ${var.session_name} 2>/dev/null; then
echo "Attaching to existing Aider tmux session..."
tmux attach-session -t ${var.session_name}
else
echo "Starting new Aider tmux session..."
tmux new-session -s ${var.session_name} -c ${var.folder} "export ${local.env_var_name}=\"${var.ai_api_key}\"; aider ${local.model_flag} ${var.ai_model} --message \"${local.combined_prompt}\"; exec bash"
fi
elif [ "${var.use_screen}" = "true" ]; then
if ! screen -list | grep -q "${var.session_name}"; then
echo "Error: No existing Aider session found. Please wait for the script to start it."
exit 1
fi
screen -xRR ${var.session_name}
else
cd "${var.folder}"
echo "Starting Aider directly..."
export ${local.env_var_name}="${var.ai_api_key}"
aider ${local.model_flag} ${var.ai_model} --message "${local.combined_prompt}"
fi
echo -n '${base64encode(local.install_script)}' | base64 -d > /tmp/install.sh
chmod +x /tmp/install.sh
ARG_WORKDIR='${var.workdir}' \
ARG_INSTALL_AIDER='${var.install_aider}' \
ARG_REPORT_TASKS='${var.report_tasks}' \
ARG_AIDER_CONFIG="$(echo -n '${base64encode(local.base_aider_config)}' | base64 -d)" \
/tmp/install.sh
EOT
order = var.order
group = var.group
}
@@ -0,0 +1,149 @@
run "test_aider_basic" {
command = plan
variables {
agent_id = "test-agent-123"
workdir = "/home/coder"
model = "gemini"
}
assert {
condition = var.workdir == "/home/coder"
error_message = "Workdir variable should default to /home/coder"
}
assert {
condition = var.agent_id == "test-agent-123"
error_message = "Agent ID variable should be set correctly"
}
assert {
condition = var.install_aider == true
error_message = "install_aider should default to true"
}
assert {
condition = var.install_agentapi == true
error_message = "install_agentapi should default to true"
}
assert {
condition = var.report_tasks == false
error_message = "report_tasks should default to false"
}
}
run "test_with_api_key" {
command = plan
variables {
agent_id = "test-agent-456"
workdir = "/home/coder/workspace"
api_key = "test-api-key-123"
model = "gemini"
}
assert {
condition = var.api_key == "test-api-key-123"
error_message = "API key value should match the input"
}
}
run "test_custom_options" {
command = plan
variables {
agent_id = "test-agent-789"
workdir = "/home/coder/custom"
order = 5
group = "development"
icon = "/icon/custom.svg"
model = "4o"
ai_prompt = "Help me write better code"
install_aider = false
install_agentapi = false
agentapi_version = "v0.10.0"
api_key = ""
base_aider_config = "read:\n - CONVENTIONS.md"
}
assert {
condition = var.order == 5
error_message = "Order variable should be set to 5"
}
assert {
condition = var.group == "development"
error_message = "Group variable should be set to 'development'"
}
assert {
condition = var.icon == "/icon/custom.svg"
error_message = "Icon variable should be set to custom icon"
}
assert {
condition = var.model == "4o"
error_message = "Model variable should be set to '4o'"
}
assert {
condition = var.ai_prompt == "Help me write better code"
error_message = "AI prompt variable should be set correctly"
}
assert {
condition = var.install_aider == false
error_message = "install_aider should be set to false"
}
assert {
condition = var.install_agentapi == false
error_message = "install_agentapi should be set to false"
}
assert {
condition = var.agentapi_version == "v0.10.0"
error_message = "AgentAPI version should be set to 'v0.10.0'"
}
}
run "test_with_scripts" {
command = plan
variables {
agent_id = "test-agent-scripts"
workdir = "/home/coder/scripts"
model = "gemini"
pre_install_script = "echo 'Pre-install script'"
post_install_script = "echo 'Post-install script'"
}
assert {
condition = var.pre_install_script == "echo 'Pre-install script'"
error_message = "Pre-install script should be set correctly"
}
assert {
condition = var.post_install_script == "echo 'Post-install script'"
error_message = "Post-install script should be set correctly"
}
}
run "test_ai_provider_env_mapping" {
command = plan
variables {
agent_id = "test-agent-provider"
workdir = "/home/coder/test"
ai_provider = "google"
model = "gemini"
custom_env_var_name = ""
}
# Ensure provider -> env var mapping works as expected (based on locals.provider_env_vars)
assert {
condition = var.ai_provider == "google"
error_message = "AI provider should be set to 'google' for this test"
}
}

Some files were not shown because too many files have changed in this diff Show More