Compare commits

..

11 Commits

Author SHA1 Message Date
Austen Bruhn a7a4ae4cdf Merge branch 'main' into aws-cli-module 2025-11-18 14:34:14 -07:00
Austen Bruhn be4dc937b5 Address PR review comments
- Add set -e for strict error handling
- Exit on version mismatch with clear error message
- Use >> for log appending (was overwriting with >)
- Add error checking after curl, unzip, and install commands
- Add macOS architecture-specific download URLs
- Move autocomplete config after successful installation verification
2025-11-15 18:33:05 -07:00
Austen Bruhn 7a70127f35 Add download_url parameter for airgapped environment support 2025-11-15 18:20:24 -07:00
Austen Bruhn 2039c3908b Update namespace profile with personal info and GitHub avatar 2025-11-15 18:12:47 -07:00
Austen Bruhn eef8697f52 Merge remote-tracking branch 'origin/main' into aws-cli-module 2025-11-15 17:54:32 -07:00
Austen Bruhn 3393a04272 Add AWS CLI autocomplete support for bash, zsh, and fish shells 2025-11-15 17:50:21 -07:00
Austen Bruhn 75fd523215 Add aws-cli module for ausbru87 namespace 2025-11-15 17:23:42 -07:00
Austen Bruhn 5564a552ba Remove trailing whitespace from README.md
Fix formatting issue on line 56 that was causing CI check to fail
2025-11-15 17:01:21 -07:00
Austen Bruhn fca4fdf2ef Fix formatting issues in AWS CLI module
- Format README.md tables with proper column alignment
- Add spaces around redirect operators in run.sh
- Remove trailing whitespace
- Update bun.lock with installed dependencies
2025-11-15 16:57:30 -07:00
Austen Bruhn c22fb8bcdc Restructure AWS CLI module to personal namespace
- Create ausbru87 namespace with avatar and README
- Move aws-cli module from registry/coder to registry/ausbru87
- Update all source paths to registry.coder.com/modules/ausbru87/aws-cli
- Follows contribution guidelines for community modules
2025-11-15 16:54:36 -07:00
Austen Bruhn fdaaba7378 Add AWS CLI module for Coder Registry
- Installs AWS CLI v2 in Coder workspaces
- Supports x86_64 and ARM64 architectures with auto-detection
- Allows version pinning or installs latest version
- Optional GPG signature verification for security
- Idempotent installation (skips if already installed)
- Supports custom installation directories
- Includes comprehensive tests (Terraform + TypeScript)
- Full documentation with multiple usage examples
2025-11-15 16:47:07 -07:00
233 changed files with 2846 additions and 10103 deletions
@@ -82,8 +82,7 @@ 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
unresolved_incidents=$(curl -s -X GET "https://api.instatus.com/v1/$INSTATUS_PAGE_ID/incidents" \
local 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')
+22 -68
View File
@@ -1,18 +1,14 @@
#!/bin/bash
# Version Bump Script
# Usage: ./version-bump.sh [--ci] <bump_type> [base_ref]
# --ci: CI mode - run bump, check for changes, exit 1 if changes needed
# Usage: ./version-bump.sh <bump_type> [base_ref]
# 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 [--ci] <bump_type> [base_ref]"
echo " --ci: CI mode - validates versions are already bumped (exits 1 if not)"
echo "Usage: $0 <bump_type> [base_ref]"
echo " bump_type: patch, minor, or major"
echo " base_ref: base reference for diff (default: origin/main)"
echo ""
@@ -20,7 +16,6 @@ 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
}
@@ -75,38 +70,21 @@ 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" '
/^[[:space:]]*module[[:space:]]/ {
in_module_block = 1
module_content = $0 "\n"
module_has_target_source = 0
next
}
in_module_block {
module_content = module_content $0 "\n"
if ($0 ~ /source.*=/ && $0 ~ module_source) {
module_has_target_source = 1
/source.*=.*/ {
if ($0 ~ module_source) {
in_target_module = 1
} else {
in_target_module = 0
}
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 = ""
}
/^[[:space:]]*version[[:space:]]*=/ {
if (in_target_module) {
match($0, /^[[:space]]*/
indent = substr($0, 1, RLENGTH)
print indent "version = \"" new_version "\""
in_target_module = 0
next
}
next
}
{ print }
' "$readme_path" > "${readme_path}.tmp" && mv "${readme_path}.tmp" "$readme_path"
@@ -120,11 +98,6 @@ update_readme_version() {
}
main() {
if [ "${1:-}" = "--ci" ]; then
CI_MODE=true
shift
fi
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
usage
fi
@@ -162,8 +135,6 @@ 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
@@ -214,7 +185,6 @@ 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
@@ -223,22 +193,19 @@ main() {
done <<< "$modules"
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 ""
# 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"
fi
echo ""
echo "📋 Summary:"
echo "Bump Type: $bump_type"
echo ""
echo "Modules Processed:"
echo "Modules Updated:"
echo -e "$bumped_modules"
echo ""
@@ -255,19 +222,6 @@ 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."
-1
View File
@@ -3,7 +3,6 @@ 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
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Run check.sh
run: |
+5 -16
View File
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Detect changed files
uses: dorny/paths-filter@v3
id: filter
@@ -29,11 +29,8 @@ jobs:
- '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
@@ -67,20 +64,12 @@ jobs:
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@v6
uses: actions/checkout@v5
- name: Install Bun
uses: oven-sh/setup-bun@v2
with:
@@ -93,7 +82,7 @@ jobs:
- name: Validate formatting
run: bun fmt:ci
- name: Check for typos
uses: crate-ci/typos@v1.41.0
uses: crate-ci/typos@v1.39.2
with:
config: .github/typos.toml
validate-readme-files:
@@ -104,11 +93,11 @@ jobs:
needs: validate-style
steps:
- name: Check out code
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.24.0"
go-version: "1.23.2"
- 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@v6
uses: actions/checkout@v5
- name: Authenticate with Google Cloud
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093
with:
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
with:
go-version: stable
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
fetch-depth: 0
persist-credentials: false
+50 -23
View File
@@ -20,7 +20,7 @@ jobs:
issues: write
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -55,35 +55,62 @@ jobs:
;;
esac
- name: Check version bump
run: ./.github/scripts/version-bump.sh --ci "${{ steps.bump-type.outputs.type }}" origin/main
- 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: Comment on PR - Version bump required
if: failure()
{
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'
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 }}`;
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');
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.`;
github.rest.issues.createComment({
issue_number: context.issue.number,
-1
View File
@@ -1,4 +1,3 @@
.DS_Store
# Logs
logs
*.log
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.3 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 35 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.5 KiB

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

Before

Width:  |  Height:  |  Size: 577 B

-5
View File
@@ -1,5 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 293 B

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

Before

Width:  |  Height:  |  Size: 216 B

-12
View File
@@ -1,12 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 727 B

-2
View File
@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" role="img" xmlns="http://www.w3.org/2000/svg"><title>Scaleway icon</title><path d="M16.61 11.11v5.72a1.77 1.77 0 0 1-1.54 1.69h-4a1.43 1.43 0 0 1-1.31-1.22 1.09 1.09 0 0 1 0-.18 1.37 1.37 0 0 1 1.37-1.36h1.74a1 1 0 0 0 1-1v-3.62a1.4 1.4 0 0 1 1.18-1.39h.17a1.37 1.37 0 0 1 1.39 1.36zm-6.46 1.74V9.26a1 1 0 0 1 1-1H13a1.37 1.37 0 0 0 1.37-1.37 1 1 0 0 0 0-.17 1.45 1.45 0 0 0-1.41-1.2H9a1.81 1.81 0 0 0-1.58 1.66v5.7a1.37 1.37 0 0 0 1.37 1.37H9a1.4 1.4 0 0 0 1.15-1.4zm12-4.29V20A4.53 4.53 0 0 1 18 24h-7.58a8.57 8.57 0 0 1-8.56-8.57V4.54A4.54 4.54 0 0 1 6.4 0h7.18a8.56 8.56 0 0 1 8.56 8.56zm-2.74 0a5.83 5.83 0 0 0-5.82-5.82H6.4a1.79 1.79 0 0 0-1.8 1.8v10.89a5.83 5.83 0 0 0 5.82 5.8h7.44a1.79 1.79 0 0 0 1.54-1.48z"/></svg>

Before

Width:  |  Height:  |  Size: 913 B

-22
View File
@@ -1,22 +0,0 @@
# 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.main.id
agent_id = coder_agent.example.id
}
```
+12 -251
View File
@@ -5,15 +5,14 @@
"": {
"name": "registry",
"devDependencies": {
"@types/bun": "^1.3.4",
"bun-types": "^1.3.4",
"dedent": "^1.7.0",
"@types/bun": "^1.2.21",
"bun-types": "^1.2.21",
"dedent": "^1.6.0",
"gray-matter": "^4.0.3",
"marked": "^16.4.2",
"prettier": "^3.7.4",
"marked": "^16.2.0",
"prettier": "^3.6.2",
"prettier-plugin-sh": "^0.18.0",
"prettier-plugin-terraform-formatter": "^1.2.1",
"shellcheck": "^4.1.0",
},
"peerDependencies": {
"typescript": "^5.8.3",
@@ -21,292 +20,54 @@
},
},
"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=="],
"@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/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
"@types/node": ["@types/node@24.0.14", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw=="],
"@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=="],
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
"argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
"bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],
"b4a": ["b4a@1.7.3", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"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=="],
"dedent": ["dedent@1.6.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA=="],
"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=="],
"make-dir": ["make-dir@1.3.0", "", { "dependencies": { "pify": "^3.0.0" } }, "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ=="],
"marked": ["marked@16.2.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-LbbTuye+0dWRz2TS9KJ7wsnD4KAtpj0MVkWc90XvBa6AslXsT0hTBVH5k32pcSyHH1fst9XEFJunXHktVy0zlg=="],
"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": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
"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.main.id
agent_id = coder_agent.example.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.main.id
agent_id = coder_agent.example.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.main.id
agent_id = coder_agent.example.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.main.id
agent_id = coder_agent.example.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.main.id
agent_id = coder_agent.example.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.main.id
agent_id = coder_agent.example.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.main.id
agent_id = coder_agent.example.id
offline = true
}
```
+4 -4
View File
@@ -1,6 +1,6 @@
module coder.com/coder-registry
go 1.24.0
go 1.23.2
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.45.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/term v0.37.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
)
+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.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/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/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/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/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=
+6 -8
View File
@@ -6,19 +6,17 @@
"terraform-validate": "./scripts/terraform_validate.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.3.4",
"bun-types": "^1.3.4",
"dedent": "^1.7.0",
"@types/bun": "^1.2.21",
"bun-types": "^1.2.21",
"dedent": "^1.6.0",
"gray-matter": "^4.0.3",
"marked": "^16.4.2",
"prettier": "^3.7.4",
"marked": "^16.2.0",
"prettier": "^3.6.2",
"prettier-plugin-sh": "^0.18.0",
"prettier-plugin-terraform-formatter": "^1.2.1",
"shellcheck": "^4.1.0"
"prettier-plugin-terraform-formatter": "^1.2.1"
},
"peerDependencies": {
"typescript": "^5.8.3"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 64 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.1"
agent_id = coder_agent.main.id
version = "1.0.0"
agent_id = coder_agent.example.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.1"
agent_id = coder_agent.main.id
version = "1.0.0"
agent_id = coder_agent.example.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.1"
agent_id = coder_agent.main.id
version = "1.0.0"
agent_id = coder_agent.example.id
rustdesk_password = "mycustompass"
xvfb_resolution = "1920x1080x24"
rustdesk_version = "1.4.1"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

-7
View File
@@ -1,7 +0,0 @@
---
display_name: "Excellencedev"
bio: "Love to contribute"
avatar: "./.images/avatar.png"
support_email: "ademiluyisuccessandexcellence@gmail.com"
status: "community"
---
@@ -1,32 +0,0 @@
---
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.
@@ -1,62 +0,0 @@
#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
@@ -1,224 +0,0 @@
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
}
coder = {
source = "coder/coder"
}
http = {
source = "hashicorp/http"
version = "~> 3.0"
}
}
}
variable "hcloud_token" {
sensitive = true
}
provider "hcloud" {
token = var.hcloud_token
}
data "http" "hcloud_locations" {
url = "https://api.hetzner.cloud/v1/locations"
request_headers = {
Authorization = "Bearer ${var.hcloud_token}"
Accept = "application/json"
}
}
data "http" "hcloud_server_types" {
url = "https://api.hetzner.cloud/v1/server_types"
request_headers = {
Authorization = "Bearer ${var.hcloud_token}"
Accept = "application/json"
}
}
# 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"
dynamic "option" {
for_each = local.hcloud_locations
content {
name = format(
"%s (%s, %s)",
upper(option.value.name),
option.value.city,
option.value.country
)
value = option.value.name
}
}
}
# 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)
# --------------------
# Locations
# --------------------
hcloud_locations = [
for loc in jsondecode(data.http.hcloud_locations.response_body).locations : {
name = loc.name
city = loc.city
country = loc.country
}
]
# --------------------
# Server Types
# --------------------
hcloud_server_types = {
for st in jsondecode(data.http.hcloud_server_types.response_body).server_types :
st.name => {
cores = st.cores
memory_gb = st.memory
disk_gb = st.disk
locations = [for l in st.locations : l.name]
deprecated = st.deprecated
}
if st.deprecated == false
}
hcloud_server_type_options_for_selected_location = [
for name, meta in local.hcloud_server_types : {
name = format(
"%s (%d vCPU, %dGB RAM, %dGB)",
upper(name),
meta.cores,
meta.memory_gb,
meta.disk_gb
)
value = name
}
if contains(
meta.locations,
data.coder_parameter.hcloud_location.value
)
]
}
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: 7.4 KiB

After

Width:  |  Height:  |  Size: 8.9 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.4"
version = "1.0.1"
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.4"
version = "1.0.1"
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.4"
version = "1.0.1"
agent_id = var.agent_id
sessions = ["default", "dev", "anomaly"]
tmux_config = <<-EOT
+1 -1
View File
@@ -55,7 +55,7 @@ resource "coder_script" "tmux" {
display_name = "tmux"
icon = "/icon/terminal.svg"
script = templatefile("${path.module}/scripts/run.sh", {
TMUX_CONFIG = base64encode(var.tmux_config)
TMUX_CONFIG = var.tmux_config
SAVE_INTERVAL = var.save_interval
})
run_on_start = true
+3 -3
View File
@@ -4,7 +4,7 @@ BOLD='\033[0;1m'
# Convert templated variables to shell variables
SAVE_INTERVAL="${SAVE_INTERVAL}"
TMUX_CONFIG=$(echo -n "${TMUX_CONFIG}" | base64 -d)
TMUX_CONFIG="${TMUX_CONFIG}"
# Function to install tmux
install_tmux() {
@@ -73,7 +73,7 @@ setup_tmux_config() {
mkdir -p "$config_dir"
if [ -n "$TMUX_CONFIG" ]; then
printf "%s" "$TMUX_CONFIG" > "$config_file"
printf "$TMUX_CONFIG" > "$config_file"
printf "$${BOLD}Custom tmux configuration applied at {$config_file} \n\n"
else
cat > "$config_file" << EOF
@@ -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 "$HOME/.tmux/plugins/tmux-resurrect/scripts/restore.sh"
tmux new-session -d \; source-file ~/.tmux.conf \; run-shell '~/.tmux/plugins/tmux-resurrect/scripts/restore.sh'
printf "$${BOLD} Sessions restored: -> %s\n" "$(tmux ls)"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

+32
View File
@@ -0,0 +1,32 @@
---
display_name: Austen Bruhn
bio: Solution Engineer at Coder, OSS enthusiast
github: ausbru87
avatar: ./.images/avatar.png
linkedin: https://www.linkedin.com/in/austen-bruhn-b0a646a1/
status: community
---
# ausbru87
Brief description of what this namespace provides. Include information about:
- What types of templates/modules you offer
- Your focus areas (e.g., specific cloud providers, technologies)
- Any special features or configurations
## Templates
List your available templates here:
- **template-name**: Brief description
## Modules
List your available modules here:
- **module-name**: Brief description
## Contributing
If you'd like to contribute to this namespace, please [open an issue](https://github.com/coder/registry/issues) or submit a pull request.
@@ -0,0 +1,78 @@
---
display_name: AWS CLI
description: Install AWS CLI v2 in your workspace
icon: ../../../../.icons/aws.svg
verified: false
tags: [helper, aws, cli]
---
# AWS CLI
Automatically install the [AWS CLI v2](https://aws.amazon.com/cli/) in your Coder workspace with command autocomplete support for bash, zsh, and fish shells.
```tf
module "aws-cli" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/ausbru87/aws-cli/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
}
```
## Features
- Installs AWS CLI v2 for Linux and macOS
- Supports x86_64 and ARM64 architectures
- Optional version pinning
- **Auto-configures command autocomplete** for bash, zsh, and fish shells
## Examples
### Basic Installation
```tf
module "aws-cli" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/ausbru87/aws-cli/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
}
```
### Pin to Specific Version
```tf
module "aws-cli" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/ausbru87/aws-cli/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
install_version = "2.15.0"
}
```
### Custom Log Path
```tf
module "aws-cli" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/ausbru87/aws-cli/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
log_path = "/var/log/aws-cli.log"
}
```
### Airgapped Environment
Use a custom download URL for environments without internet access to AWS:
```tf
module "aws-cli" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/ausbru87/aws-cli/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
download_url = "https://internal-mirror.company.com/awscli-exe-linux-x86_64.zip"
}
```
@@ -0,0 +1,34 @@
run "required_vars" {
command = plan
variables {
agent_id = "test-agent-id"
}
}
run "with_custom_version" {
command = plan
variables {
agent_id = "test-agent-id"
install_version = "2.15.0"
}
}
run "with_custom_log_path" {
command = plan
variables {
agent_id = "test-agent-id"
log_path = "/var/log/aws-cli.log"
}
}
run "with_custom_download_url" {
command = plan
variables {
agent_id = "test-agent-id"
download_url = "https://internal-mirror.company.com/awscli-exe-linux-x86_64.zip"
}
}
+46
View File
@@ -0,0 +1,46 @@
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 "install_version" {
type = string
description = "The version of AWS CLI to install."
default = ""
}
variable "download_url" {
type = string
description = "Custom download URL for AWS CLI. Useful for airgapped environments. If not set, uses the official AWS download URL."
default = ""
}
variable "log_path" {
type = string
description = "The path to the AWS CLI installation log file."
default = "/tmp/aws-cli-install.log"
}
resource "coder_script" "aws-cli" {
agent_id = var.agent_id
display_name = "AWS CLI"
icon = "/icon/aws.svg"
script = templatefile("${path.module}/run.sh", {
LOG_PATH : var.log_path,
VERSION : var.install_version,
DOWNLOAD_URL : var.download_url,
})
run_on_start = true
run_on_stop = false
}
+129
View File
@@ -0,0 +1,129 @@
#!/usr/bin/env sh
set -e
LOG_PATH=${LOG_PATH}
VERSION=${VERSION}
DOWNLOAD_URL=${DOWNLOAD_URL}
BOLD='\\033[0;1m'
RESET='\\033[0m'
printf "${BOLD}Installing AWS CLI...\\n${RESET}"
# Check if AWS CLI is already installed
if command -v aws > /dev/null 2>&1; then
INSTALLED_VERSION=$(aws --version 2>&1 | cut -d' ' -f1 | cut -d'/' -f2)
if [ -n "$VERSION" ] && [ "$INSTALLED_VERSION" != "$VERSION" ]; then
printf "❌ AWS CLI $INSTALLED_VERSION is installed, but version $VERSION was requested.\\n"
printf "Note: AWS CLI installer does not support version-specific installation.\\n"
exit 1
else
printf "AWS CLI is already installed ($INSTALLED_VERSION). Skipping installation.\\n"
exit 0
fi
fi
# Determine OS and architecture
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
case "$ARCH" in
x86_64) ARCH="x86_64" ;;
aarch64 | arm64) ARCH="aarch64" ;;
*)
printf "Unsupported architecture: $ARCH\\n" >> "${LOG_PATH}" 2>&1
exit 1
;;
esac
# Install AWS CLI
if [ "$OS" = "linux" ]; then
# Use custom download URL if provided, otherwise use default AWS URL
if [ -z "$DOWNLOAD_URL" ]; then
DOWNLOAD_URL="https://awscli.amazonaws.com/awscli-exe-linux-${ARCH}.zip"
fi
printf "Downloading AWS CLI from $DOWNLOAD_URL...\\n"
curl -fsSL "$DOWNLOAD_URL" -o /tmp/awscliv2.zip >> "${LOG_PATH}" 2>&1 || exit 1
unzip -q /tmp/awscliv2.zip -d /tmp >> "${LOG_PATH}" 2>&1 || exit 1
sudo /tmp/aws/install >> "${LOG_PATH}" 2>&1 || exit 1
rm -rf /tmp/awscliv2.zip /tmp/aws
elif [ "$OS" = "darwin" ]; then
# Use custom download URL if provided, otherwise use architecture-specific AWS URL
if [ -z "$DOWNLOAD_URL" ]; then
case "$ARCH" in
x86_64)
DOWNLOAD_URL="https://awscli.amazonaws.com/AWSCLIV2-x86_64.pkg"
;;
aarch64)
DOWNLOAD_URL="https://awscli.amazonaws.com/AWSCLIV2-arm64.pkg"
;;
*)
DOWNLOAD_URL="https://awscli.amazonaws.com/AWSCLIV2.pkg"
;;
esac
fi
printf "Downloading AWS CLI from $DOWNLOAD_URL...\\n"
curl -fsSL "$DOWNLOAD_URL" -o /tmp/AWSCLIV2.pkg >> "${LOG_PATH}" 2>&1 || exit 1
sudo installer -pkg /tmp/AWSCLIV2.pkg -target / >> "${LOG_PATH}" 2>&1 || exit 1
rm -f /tmp/AWSCLIV2.pkg
else
printf "Unsupported OS: $OS\\n" >> "${LOG_PATH}" 2>&1
exit 1
fi
# Verify installation was successful
if command -v aws > /dev/null 2>&1; then
printf "🥳 AWS CLI installed successfully!\\n"
aws --version
# Configure autocomplete for common shells
if command -v aws_completer > /dev/null 2>&1; then
AWS_COMPLETER_PATH=$(which aws_completer)
# Bash autocomplete
if [ -f ~/.bashrc ]; then
if ! grep -q "aws_completer.*aws" ~/.bashrc; then
echo "complete -C '$AWS_COMPLETER_PATH' aws" >> ~/.bashrc
printf "✓ Configured AWS CLI autocomplete for bash\\n"
fi
fi
# Zsh autocomplete
if [ -f ~/.zshrc ] || [ -d ~/.oh-my-zsh ]; then
if ! grep -q "aws_completer.*aws" ~/.zshrc 2> /dev/null; then
cat >> ~/.zshrc << ZSHEOF
# AWS CLI autocomplete
autoload bashcompinit && bashcompinit
autoload -Uz compinit && compinit
complete -C '$AWS_COMPLETER_PATH' aws
ZSHEOF
printf "✓ Configured AWS CLI autocomplete for zsh\\n"
fi
fi
# Fish autocomplete
if [ -d ~/.config/fish ] || command -v fish > /dev/null 2>&1; then
mkdir -p ~/.config/fish/completions
FISH_COMPLETION=~/.config/fish/completions/aws.fish
if [ ! -f "$FISH_COMPLETION" ]; then
cat > "$FISH_COMPLETION" << 'FISHEOF'
complete --command aws --no-files --arguments '(begin; set --local --export COMP_SHELL fish; set --local --export COMP_LINE (commandline); aws_completer | sed '"'"'s/ $//'"'"'; end)'
FISHEOF
printf "✓ Configured AWS CLI autocomplete for fish\\n"
fi
fi
fi
else
printf "❌ AWS CLI installation failed. Check logs at ${LOG_PATH}\\n"
exit 1
fi
Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 590 KiB

@@ -14,7 +14,7 @@ This module installs small, robust scripts in your workspace to create and extra
module "archive" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/archive/coder"
version = "0.0.3"
version = "0.0.1"
agent_id = coder_agent.example.id
paths = ["./projects", "./code"]
@@ -43,7 +43,7 @@ Basic example:
module "archive" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/archive/coder"
version = "0.0.3"
version = "0.0.1"
agent_id = coder_agent.example.id
# Paths to include in the archive (files or directories).
@@ -61,7 +61,7 @@ Customize compression and output:
module "archive" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/archive/coder"
version = "0.0.3"
version = "0.0.1"
agent_id = coder_agent.example.id
directory = "/"
@@ -78,7 +78,7 @@ Enable auto-archive on stop:
module "archive" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/archive/coder"
version = "0.0.3"
version = "0.0.1"
agent_id = coder_agent.example.id
# Creates /tmp/coder-archive.tar.gz of the users home directory (defaults).
@@ -92,7 +92,7 @@ Extract on start:
module "archive" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/archive/coder"
version = "0.0.3"
version = "0.0.1"
agent_id = coder_agent.example.id
# Where to look for the archive file to extract:
+2 -3
View File
@@ -6,8 +6,8 @@ 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_PATHS=(${TF_PATHS})
DEFAULT_EXCLUDE_PATTERNS=(${TF_EXCLUDE_PATTERNS})
DEFAULT_COMPRESSION="${TF_COMPRESSION}"
DEFAULT_ARCHIVE_PATH="${TF_ARCHIVE_PATH}"
DEFAULT_DIRECTORY="${TF_DIRECTORY}"
@@ -62,7 +62,6 @@ 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 || {
+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.3.0"
version = "0.2.1"
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 = "0.2.2"
version = "1.0.31"
agent_id = coder_agent.example.id
}
module "auggie" {
source = "registry.coder.com/coder-labs/auggie/coder"
version = "0.3.0"
version = "0.2.1"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
@@ -57,7 +57,7 @@ module "auggie" {
EOF # Required for tasks
# Version
auggie_version = "0.2.2"
auggie_version = "0.3.0"
# 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.3.0"
version = "0.2.1"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
+3 -7
View File
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.12"
version = ">= 2.7"
}
}
}
@@ -179,7 +179,7 @@ locals {
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "2.0.0"
version = "1.2.0"
agent_id = var.agent_id
folder = local.folder
@@ -229,8 +229,4 @@ 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,11 +1,8 @@
#!/bin/bash
if [ -f "$HOME/.bashrc" ]; then
source "$HOME"/.bashrc
fi
set -euo pipefail
source "$HOME"/.bashrc
BOLD='\033[0;1m'
# Function to check if a command exists
@@ -1,11 +1,8 @@
#!/bin/bash
if [ -f "$HOME/.bashrc" ]; then
source "$HOME"/.bashrc
fi
set -euo pipefail
source "$HOME"/.bashrc
command_exists() {
command -v "$1" > /dev/null 2>&1
}
+5 -5
View File
@@ -13,7 +13,7 @@ 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 = "3.1.1"
version = "3.1.0"
agent_id = coder_agent.example.id
openai_api_key = var.openai_api_key
workdir = "/home/coder/project"
@@ -33,7 +33,7 @@ module "codex" {
module "codex" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/codex/coder"
version = "3.1.1"
version = "3.1.0"
agent_id = coder_agent.example.id
openai_api_key = "..."
workdir = "/home/coder/project"
@@ -55,13 +55,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 = "3.1.1"
version = "1.0.31"
agent_id = coder_agent.example.id
}
module "codex" {
source = "registry.coder.com/coder-labs/codex/coder"
version = "3.1.1"
version = "3.1.0"
agent_id = coder_agent.example.id
openai_api_key = "..."
ai_prompt = data.coder_parameter.ai_prompt.value
@@ -108,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 = "3.1.1"
version = "3.1.0"
# ... other variables ...
# Override default configuration
@@ -38,8 +38,7 @@ find_session_for_directory() {
return 1
fi
local session_id
session_id=$(grep "^$target_dir|" "$SESSION_TRACKING_FILE" | cut -d'|' -f2 | head -1)
local session_id=$(grep "^$target_dir|" "$SESSION_TRACKING_FILE" | cut -d'|' -f2 | head -1)
if [ -n "$session_id" ]; then
echo "$session_id"
@@ -75,12 +74,9 @@ find_recent_session_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)
local file_time=$(stat -c %Y "$session_file" 2> /dev/null || stat -f %m "$session_file" 2> /dev/null || echo "0")
local first_line=$(head -n 1 "$session_file" 2> /dev/null)
local 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"
@@ -89,10 +85,8 @@ find_recent_session_file() {
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)
local first_line=$(head -n 1 "$latest_file")
local session_id=$(echo "$first_line" | grep -o '"id":"[^"]*"' | cut -d'"' -f4)
if [ -n "$session_id" ]; then
echo "$session_id"
return 0
@@ -108,8 +102,7 @@ wait_for_session_file() {
local attempt=0
while [ $attempt -lt $max_attempts ]; do
local session_id
session_id=$(find_recent_session_file "$target_dir" 2> /dev/null || echo "")
local session_id=$(find_recent_session_file "$target_dir" 2> /dev/null || echo "")
if [ -n "$session_id" ]; then
echo "$session_id"
return 0
@@ -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.3.0"
version = "0.2.2"
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.3.0"
version = "0.2.2"
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.3.0"
version = "0.2.2"
agent_id = coder_agent.example.id
workdir = "/home/coder/projects"
# Version pinning (defaults to "latest", use specific version if desired)
copilot_version = "0.2.3"
copilot_version = "0.0.334"
# 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.3.0"
version = "0.2.2"
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.3.0"
version = "0.2.2"
agent_id = coder_agent.example.id
workdir = "/home/coder"
report_tasks = false
+5 -9
View File
@@ -3,7 +3,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.12"
version = ">= 2.7"
}
}
}
@@ -242,7 +242,7 @@ resource "coder_env" "github_token" {
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "2.0.0"
version = "1.2.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,8 +299,4 @@ module "agentapi" {
ARG_COPILOT_MODEL='${var.copilot_model}' \
/tmp/install.sh
EOT
}
output "task_app_id" {
value = module.agentapi.task_app_id
}
}
@@ -1,11 +1,8 @@
#!/bin/bash
if [ -f "$HOME/.bashrc" ]; then
source "$HOME"/.bashrc
fi
set -euo pipefail
source "$HOME"/.bashrc
command_exists() {
command -v "$1" > /dev/null 2>&1
}
@@ -1,11 +1,7 @@
#!/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.3.0"
agent_id = coder_agent.main.id
version = "0.2.1"
agent_id = coder_agent.example.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.3.0"
agent_id = coder_agent.main.id
version = "0.2.1"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
# Optional
@@ -60,7 +60,6 @@ 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(`--model ${model}`);
expect(startLog.stdout).toContain(`-m ${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.12"
version = ">= 2.7"
}
}
}
@@ -132,7 +132,7 @@ resource "coder_env" "cursor_api_key" {
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "2.0.0"
version = "1.2.0"
agent_id = var.agent_id
folder = local.folder
@@ -179,7 +179,3 @@ 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+=("--model" "$ARG_MODEL")
ARGS+=("-m" "$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 = "3.0.0"
agent_id = coder_agent.main.id
version = "2.1.1"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
}
```
@@ -46,8 +46,8 @@ variable "gemini_api_key" {
module "gemini" {
source = "registry.coder.com/coder-labs/gemini/coder"
version = "3.0.0"
agent_id = coder_agent.main.id
version = "2.1.1"
agent_id = coder_agent.example.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.main.id
agent_id = coder_agent.example.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 = "3.0.0"
agent_id = coder_agent.main.id
version = "2.1.1"
agent_id = coder_agent.example.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 = "3.0.0"
agent_id = coder_agent.main.id
version = "2.1.1"
agent_id = coder_agent.example.id
gemini_api_key = var.gemini_api_key
folder = "/home/coder/project"
use_vertexai = true
+3 -7
View File
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.12"
version = ">= 2.7"
}
}
}
@@ -177,7 +177,7 @@ EOT
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "2.0.0"
version = "1.2.0"
agent_id = var.agent_id
folder = local.folder
@@ -225,8 +225,4 @@ 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.1"
agent_id = coder_agent.main.id
version = "0.9.0"
agent_id = coder_agent.example.id
}
```
@@ -1,64 +0,0 @@
---
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"
}
```
@@ -1,94 +0,0 @@
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
}
@@ -1,188 +0,0 @@
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"
}
}
@@ -1,66 +0,0 @@
#!/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}"
@@ -1,109 +0,0 @@
---
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)
@@ -1,362 +0,0 @@
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);
});
});
@@ -1,203 +0,0 @@
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
}
@@ -1,374 +0,0 @@
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"
}
}
@@ -1,131 +0,0 @@
#!/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."
@@ -1,71 +0,0 @@
#!/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
@@ -1,25 +0,0 @@
#!/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
@@ -1,55 +0,0 @@
---
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"
}
```
@@ -1,108 +0,0 @@
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
}
@@ -1,26 +0,0 @@
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"
}
}
@@ -1,67 +0,0 @@
#!/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"
@@ -12,12 +12,12 @@ 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 = "3.0.0"
agent_id = coder_agent.example.id
amp_api_key = var.amp_api_key
install_amp = true
agentapi_version = "latest"
source = "registry.coder.com/coder-labs/sourcegraph-amp/coder"
version = "2.0.1"
agent_id = coder_agent.example.id
sourcegraph_amp_api_key = var.sourcegraph_amp_api_key
install_sourcegraph_amp = true
agentapi_version = "latest"
}
```
@@ -48,7 +48,7 @@ variable "amp_api_key" {
module "amp-cli" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder-labs/sourcegraph-amp/coder"
amp_version = "3.0.0"
amp_version = "2.0.1"
agent_id = coder_agent.example.id
amp_api_key = var.amp_api_key # recommended for tasks usage
workdir = "/home/coder/project"
@@ -110,7 +110,6 @@ describe("amp", async () => {
const { id } = await setup({
skipAmpMock: true,
moduleVariables: {
install_via_npm: "true",
amp_version: "0.0.1755964909-g31e083",
},
});
@@ -4,7 +4,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.12"
version = ">= 2.7"
}
external = {
source = "hashicorp/external"
@@ -55,7 +55,7 @@ variable "install_agentapi" {
variable "agentapi_version" {
type = string
description = "The version of AgentAPI to install."
default = "v0.11.1"
default = "v0.10.0"
}
variable "cli_app" {
@@ -140,7 +140,7 @@ 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)
@@ -148,7 +148,7 @@ 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 = ""
@@ -160,16 +160,6 @@ variable "mcp" {
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'\"}'"]
}
@@ -180,7 +170,6 @@ locals {
default_base_config = jsonencode({
"amp.anthropic.thinking.enabled" = true
"amp.todos.enabled" = true
"amp.terminal.animation" = false
})
user_config = jsondecode(var.base_amp_config != "" ? var.base_amp_config : local.default_base_config)
@@ -220,7 +209,7 @@ locals {
module "agentapi" {
source = "registry.coder.com/coder/agentapi/coder"
version = "2.0.0"
version = "1.2.0"
agent_id = var.agent_id
folder = local.workdir
@@ -248,7 +237,6 @@ module "agentapi" {
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
@@ -268,6 +256,4 @@ module "agentapi" {
EOT
}
output "task_app_id" {
value = module.agentapi.task_app_id
}
@@ -1,6 +1,8 @@
#!/bin/bash
set -euo pipefail
source "$HOME"/.bashrc
# ANSI colors
BOLD='\033[1m'
GREEN='\033[0;32m'
@@ -1,16 +1,14 @@
#!/bin/bash
set -euo pipefail
# Load user environment
if [ -f "$HOME/.bashrc" ]; then
source "$HOME/.bashrc"
fi
# shellcheck source=/dev/null
source "$HOME/.bashrc"
# shellcheck source=/dev/null
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() {
@@ -29,7 +27,6 @@ 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
@@ -51,13 +48,6 @@ else
printf "amp_api_key not provided\n"
fi
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"
@@ -66,8 +56,8 @@ if [ -n "$ARG_AMP_TASK_PROMPT" ]; then
PROMPT="$ARG_AMP_TASK_PROMPT"
fi
# Pipe the prompt into amp, which will be run inside agentapi
agentapi server --type amp --term-width=67 --term-height=1190 -- bash -c "echo \"$PROMPT\" | amp" "${ARGS[@]}"
agentapi server --type amp --term-width=67 --term-height=1190 -- bash -c "echo \"$PROMPT\" | amp"
else
printf "No task prompt given.\n"
agentapi server --type amp --term-width=67 --term-height=1190 -- amp "${ARGS[@]}"
agentapi server --type amp --term-width=67 --term-height=1190 -- amp
fi
Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 KiB

After

Width:  |  Height:  |  Size: 976 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 302 KiB

@@ -3,57 +3,30 @@ set -o errexit
set -o pipefail
port=${1:-3284}
start_timeout=${2:-60}
listen_timeout=${3:-60}
# This script waits for the agentapi server to start on port 3284.
# It considers the server started after 3 consecutive successful responses.
agentapi_started=false
echo "Waiting for agentapi process to start..."
start=$(date +%s)
while true; do
now=$(date +%s)
elapsed=$(( now - start ))
if [[ "${elapsed}" -gt "${start_timeout}" ]]; then
echo "agentapi process not found after ${start_timeout} seconds"
exit 1
fi
set +e
agentapi_pid=$(pidof agentapi)
set -e
if [[ -z "${agentapi_pid}" ]]; then
echo "agentapi process not found (${elapsed}/${start_timeout})"
sleep 1
continue
fi
echo "agentapi process started with pid ${agentapi_pid} after ${elapsed} seconds"
break
done
echo "Waiting for agentapi to start listening on port ${port}..."
start=$(date +%s)
while true; do
now=$(date +%s)
elapsed=$(( now - start ))
if [[ "${elapsed}" -gt "${listen_timeout}" ]]; then
echo "agentapi server not listening on port ${port} after ${listen_timeout} seconds"
exit 1
fi
echo "Waiting for agentapi server to start on port $port..."
for i in $(seq 1 150); do
for j in $(seq 1 3); do
if curl -fs -o /dev/null "http://localhost:${port}/status"; then
echo "agentapi response received (${j}/3)"
sleep 0.1
continue
sleep 0.1
if curl -fs -o /dev/null "http://localhost:$port/status"; then
echo "agentapi response received ($j/3)"
else
echo "agentapi server not responding (${elapsed}/${listen_timeout})"
sleep 1
echo "agentapi server not responding ($i/15)"
continue 2
fi
done
echo "agentapi server started responding after ${elapsed} seconds"
agentapi_started=true
break
done
echo "agentapi server started on port ${port}."
if [ "$agentapi_started" != "true" ]; then
echo "Error: agentapi server did not start on port $port after 15 seconds."
exit 1
fi
echo "agentapi server started on port $port."
+15 -41
View File
@@ -4,35 +4,11 @@ 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,
@@ -47,12 +23,10 @@ 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" ||
@@ -105,11 +79,9 @@ interface SetupProps {
agentapiMockScript?: string;
}
export const setup = async (
props: SetupProps,
): Promise<{ id: string; coderEnvVars: Record<string, string> }> => {
export const setup = async (props: SetupProps): Promise<{ id: string }> => {
const projectDir = props.projectDir ?? "/home/coder/project";
const { id, coderScript, coderEnvVars, cleanup } = await setupContainer({
const { id, coderScript, cleanup } = await setupContainer({
moduleDir: props.moduleDir,
vars: props.moduleVariables,
});
@@ -129,7 +101,7 @@ export const setup = async (
filePath: "/home/coder/script.sh",
content: coderScript.script,
});
return { id, coderEnvVars };
return { id };
};
export const expectAgentAPIStarted = async (
@@ -153,16 +125,18 @@ export const execModuleScript = async (
id: string,
env?: Record<string, string>,
) => {
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`,
]);
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,
);
if (resp.exitCode !== 0) {
console.log(resp.stdout);
console.log(resp.stderr);
+6 -6
View File
@@ -19,8 +19,8 @@ variable "api_key" {
module "aider" {
source = "registry.coder.com/coder/aider/coder"
version = "2.0.1"
agent_id = coder_agent.main.id
version = "2.0.0"
agent_id = coder_agent.example.id
api_key = var.api_key
ai_provider = "google"
model = "gemini"
@@ -50,8 +50,8 @@ variable "gemini_api_key" {
module "aider" {
source = "registry.coder.com/coder/aider/coder"
version = "2.0.1"
agent_id = coder_agent.main.id
version = "2.0.0"
agent_id = coder_agent.example.id
api_key = var.gemini_api_key
install_aider = true
workdir = "/home/coder"
@@ -75,8 +75,8 @@ variable "custom_api_key" {
module "aider" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/aider/coder"
version = "2.0.1"
agent_id = coder_agent.main.id
version = "2.0.0"
agent_id = coder_agent.example.id
workdir = "/home/coder"
ai_provider = "custom"
custom_env_var_name = "MY_CUSTOM_API_KEY"
@@ -19,7 +19,7 @@ module "dcv" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/amazon-dcv-windows/coder"
version = "1.1.1"
agent_id = coder_agent.main.id
agent_id = resource.coder_agent.main.id
}
resource "coder_metadata" "dcv" {
+18 -19
View File
@@ -13,8 +13,8 @@ Run [Amazon Q](https://aws.amazon.com/q/) in your workspace to access Amazon's A
```tf
module "amazon-q" {
source = "registry.coder.com/coder/amazon-q/coder"
version = "3.0.1"
agent_id = coder_agent.main.id
version = "3.0.0"
agent_id = coder_agent.example.id
workdir = "/home/coder"
# Required: Authentication tarball (see below for generation)
@@ -102,8 +102,8 @@ data "coder_parameter" "ai_prompt" {
module "amazon-q" {
source = "registry.coder.com/coder/amazon-q/coder"
version = "3.0.1"
agent_id = coder_agent.main.id
version = "3.0.0"
agent_id = coder_agent.example.id
workdir = "/home/coder"
auth_tarball = var.amazon_q_auth_tarball
ai_prompt = data.coder_parameter.ai_prompt.value
@@ -228,8 +228,8 @@ If no custom `agent_config` is provided, the default agent name "agent" is used.
```tf
module "amazon-q" {
source = "registry.coder.com/coder/amazon-q/coder"
version = "3.0.1"
agent_id = coder_agent.main.id
version = "3.0.0"
agent_id = coder_agent.example.id
workdir = "/home/coder"
auth_tarball = var.amazon_q_auth_tarball
}
@@ -258,8 +258,8 @@ This example will:
```tf
module "amazon-q" {
source = "registry.coder.com/coder/amazon-q/coder"
version = "3.0.1"
agent_id = coder_agent.main.id
version = "3.0.0"
agent_id = coder_agent.example.id
workdir = "/home/coder"
auth_tarball = var.amazon_q_auth_tarball
ai_prompt = "Help me set up a Python FastAPI project with proper testing structure"
@@ -279,8 +279,8 @@ module "amazon-q" {
```tf
module "amazon-q" {
source = "registry.coder.com/coder/amazon-q/coder"
version = "3.0.1"
agent_id = coder_agent.main.id
version = "3.0.0"
agent_id = coder_agent.example.id
workdir = "/home/coder"
auth_tarball = var.amazon_q_auth_tarball
@@ -305,8 +305,8 @@ module "amazon-q" {
```tf
module "amazon-q" {
source = "registry.coder.com/coder/amazon-q/coder"
version = "3.0.1"
agent_id = coder_agent.main.id
version = "3.0.0"
agent_id = coder_agent.example.id
workdir = "/home/coder"
auth_tarball = var.amazon_q_auth_tarball
amazon_q_version = "1.14.0" # Specific version
@@ -319,8 +319,8 @@ module "amazon-q" {
```tf
module "amazon-q" {
source = "registry.coder.com/coder/amazon-q/coder"
version = "3.0.1"
agent_id = coder_agent.main.id
version = "3.0.0"
agent_id = coder_agent.example.id
workdir = "/home/coder"
auth_tarball = var.amazon_q_auth_tarball
@@ -331,7 +331,6 @@ module "amazon-q" {
"prompt": "You are a specialized DevOps assistant...",
"tools": ["fs_read", "fs_write", "execute_bash", "use_aws"]
}
EOT
}
```
@@ -341,8 +340,8 @@ module "amazon-q" {
```tf
module "amazon-q" {
source = "registry.coder.com/coder/amazon-q/coder"
version = "3.0.1"
agent_id = coder_agent.main.id
version = "3.0.0"
agent_id = coder_agent.example.id
workdir = "/home/coder"
auth_tarball = var.amazon_q_auth_tarball
@@ -359,8 +358,8 @@ For environments without direct internet access, you can host Amazon Q installat
```tf
module "amazon-q" {
source = "registry.coder.com/coder/amazon-q/coder"
version = "3.0.1"
agent_id = coder_agent.main.id
version = "3.0.0"
agent_id = coder_agent.example.id
workdir = "/home/coder"
auth_tarball = var.amazon_q_auth_tarball
@@ -1,67 +0,0 @@
---
display_name: Antigravity
description: Add a one-click button to launch Google Antigravity
icon: ../../../../.icons/antigravity.svg
verified: true
tags: [ide, antigravity, ai, google]
---
# Antigravity IDE
Add a button to open any workspace with a single click in [Antigravity IDE](https://antigravity.google).
Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder).
```tf
module "antigravity" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/antigravity/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
}
```
## Examples
### Open in a specific directory
```tf
module "antigravity" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/antigravity/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
}
```
### Configure MCP servers for Antigravity
Provide a JSON-encoded string via the `mcp` input. When set, the module writes the value to `~/.gemini/antigravity/mcp_config.json` using a `coder_script` on workspace start.
The following example configures Antigravity to use the GitHub MCP server with authentication facilitated by the [`coder_external_auth`](https://coder.com/docs/admin/external-auth#configure-a-github-oauth-app) resource.
```tf
module "antigravity" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/antigravity/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
mcp = jsonencode({
mcpServers = {
"github" : {
"url" : "https://api.githubcopilot.com/mcp/",
"headers" : {
"Authorization" : "Bearer ${data.coder_external_auth.github.access_token}",
},
"type" : "http"
}
}
})
}
data "coder_external_auth" "github" {
id = "github"
}
```
@@ -1,130 +0,0 @@
import { describe, it, expect } from "bun:test";
import {
runTerraformApply,
runTerraformInit,
testRequiredVariables,
runContainer,
execContainer,
removeContainer,
findResourceInstance,
readFileContainer,
} from "~test";
describe("antigravity", async () => {
await runTerraformInit(import.meta.dir);
testRequiredVariables(import.meta.dir, {
agent_id: "foo",
});
it("default output", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
});
expect(state.outputs.antigravity_url.value).toBe(
"antigravity://coder.coder-remote/open?owner=default&workspace=default&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
const coder_app = state.resources.find(
(res) =>
res.type === "coder_app" &&
res.module === "module.vscode-desktop-core" &&
res.name === "vscode-desktop",
);
expect(coder_app).not.toBeNull();
expect(coder_app?.instances.length).toBe(1);
expect(coder_app?.instances[0].attributes.order).toBeNull();
});
it("adds folder", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
});
expect(state.outputs.antigravity_url.value).toBe(
"antigravity://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});
it("adds folder and open_recent", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
open_recent: "true",
});
expect(state.outputs.antigravity_url.value).toBe(
"antigravity://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});
it("adds folder but not open_recent", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
open_recent: "false",
});
expect(state.outputs.antigravity_url.value).toBe(
"antigravity://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});
it("adds open_recent", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
open_recent: "true",
});
expect(state.outputs.antigravity_url.value).toBe(
"antigravity://coder.coder-remote/open?owner=default&workspace=default&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});
it("expect order to be set", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
order: "22",
});
const coder_app = state.resources.find(
(res) =>
res.type === "coder_app" &&
res.module === "module.vscode-desktop-core" &&
res.name === "vscode-desktop",
);
expect(coder_app).not.toBeNull();
expect(coder_app?.instances.length).toBe(1);
expect(coder_app?.instances[0].attributes.order).toBe(22);
});
it("writes ~/.gemini/antigravity/mcp_config.json when mcp provided", async () => {
const id = await runContainer("alpine");
try {
const mcp = JSON.stringify({
servers: { demo: { url: "http://localhost:1234" } },
});
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
mcp,
});
const script = findResourceInstance(
state,
"coder_script",
"antigravity_mcp",
).script;
const resp = await execContainer(id, ["sh", "-c", script]);
if (resp.exitCode !== 0) {
console.log(resp.stdout);
console.log(resp.stderr);
}
expect(resp.exitCode).toBe(0);
const content = await readFileContainer(
id,
"/root/.gemini/antigravity/mcp_config.json",
);
expect(content).toBe(mcp);
} finally {
await removeContainer(id);
}
}, 10000);
});
-104
View File
@@ -1,104 +0,0 @@
terraform {
required_version = ">= 1.0"
required_providers {
coder = {
source = "coder/coder"
version = ">= 2.5"
}
}
}
variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}
variable "folder" {
type = string
description = "The folder to open in Antigravity IDE."
default = ""
}
variable "open_recent" {
type = bool
description = "Open the most recent workspace or folder. Falls back to the folder if there is no recent workspace or folder to open."
default = false
}
variable "order" {
type = number
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
default = null
}
variable "group" {
type = string
description = "The name of a group that this app belongs to."
default = null
}
variable "slug" {
type = string
description = "The slug of the app."
default = "antigravity"
}
variable "display_name" {
type = string
description = "The display name of the app."
default = "Antigravity IDE"
}
variable "mcp" {
type = string
description = "JSON-encoded string to configure MCP servers for Antigravity. When set, writes ~/.gemini/antigravity/mcp_config.json."
default = ""
}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
locals {
mcp_b64 = var.mcp != "" ? base64encode(var.mcp) : ""
}
module "vscode-desktop-core" {
source = "registry.coder.com/coder/vscode-desktop-core/coder"
version = "1.0.1"
agent_id = var.agent_id
web_app_icon = "/icon/antigravity.svg"
web_app_slug = var.slug
web_app_display_name = var.display_name
web_app_order = var.order
web_app_group = var.group
folder = var.folder
open_recent = var.open_recent
protocol = "antigravity"
}
resource "coder_script" "antigravity_mcp" {
count = var.mcp != "" ? 1 : 0
agent_id = var.agent_id
display_name = "Antigravity MCP"
icon = "/icon/antigravity.svg"
run_on_start = true
start_blocks_login = false
script = <<-EOT
#!/bin/sh
set -eu
mkdir -p "$HOME/.gemini/antigravity"
echo -n "${local.mcp_b64}" | base64 -d > "$HOME/.gemini/antigravity/mcp_config.json"
chmod 600 "$HOME/.gemini/antigravity/mcp_config.json"
EOT
}
output "antigravity_url" {
value = module.vscode-desktop-core.ide_uri
description = "Antigravity IDE URL."
}
+29 -30
View File
@@ -13,8 +13,8 @@ Run the [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.9"
agent_id = coder_agent.main.id
version = "4.2.0"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
}
@@ -45,15 +45,13 @@ This example shows how to configure the Claude Code module to run the agent behi
```tf
module "claude-code" {
source = "dev.registry.coder.com/coder/claude-code/coder"
version = "4.2.9"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_boundary = true
boundary_version = "main"
boundary_log_dir = "/tmp/boundary_logs"
boundary_log_level = "WARN"
boundary_additional_allowed_urls = ["GET *google.com"]
boundary_proxy_port = "8087"
version = "3.4.3"
}
```
@@ -72,16 +70,16 @@ data "coder_parameter" "ai_prompt" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.9"
agent_id = coder_agent.main.id
version = "4.2.0"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
# OR
claude_code_oauth_token = "xxxxx-xxxx-xxxx"
claude_code_version = "2.0.62" # Pin to a specific version
agentapi_version = "0.11.4"
claude_code_version = "1.0.82" # Pin to a specific version
agentapi_version = "v0.10.0"
ai_prompt = data.coder_parameter.ai_prompt.value
model = "sonnet"
@@ -92,7 +90,7 @@ module "claude-code" {
{
"mcpServers": {
"my-custom-tool": {
"command": "my-tool-server",
"command": "my-tool-server"
"args": ["--port", "8080"]
}
}
@@ -108,12 +106,13 @@ Run and configure Claude Code as a standalone CLI in your workspace.
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.9"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
version = "4.2.0"
agent_id = coder_agent.example.id
workdir = "/home/coder"
install_claude_code = true
claude_code_version = "2.0.62"
claude_code_version = "latest"
report_tasks = false
cli_app = true
}
```
@@ -130,8 +129,8 @@ variable "claude_code_oauth_token" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.9"
agent_id = coder_agent.main.id
version = "4.2.0"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token
}
@@ -147,13 +146,13 @@ Configure Claude Code to use AWS Bedrock for accessing Claude models through you
```tf
resource "coder_env" "bedrock_use" {
agent_id = coder_agent.main.id
agent_id = coder_agent.example.id
name = "CLAUDE_CODE_USE_BEDROCK"
value = "1"
}
resource "coder_env" "aws_region" {
agent_id = coder_agent.main.id
agent_id = coder_agent.example.id
name = "AWS_REGION"
value = "us-east-1" # Choose your preferred region
}
@@ -175,13 +174,13 @@ variable "aws_secret_access_key" {
}
resource "coder_env" "aws_access_key_id" {
agent_id = coder_agent.main.id
agent_id = coder_agent.example.id
name = "AWS_ACCESS_KEY_ID"
value = var.aws_access_key_id
}
resource "coder_env" "aws_secret_access_key" {
agent_id = coder_agent.main.id
agent_id = coder_agent.example.id
name = "AWS_SECRET_ACCESS_KEY"
value = var.aws_secret_access_key
}
@@ -196,15 +195,15 @@ variable "aws_bearer_token_bedrock" {
}
resource "coder_env" "bedrock_api_key" {
agent_id = coder_agent.main.id
agent_id = coder_agent.example.id
name = "AWS_BEARER_TOKEN_BEDROCK"
value = var.aws_bearer_token_bedrock
}
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.9"
agent_id = coder_agent.main.id
version = "4.2.0"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
}
@@ -229,39 +228,39 @@ variable "vertex_sa_json" {
}
resource "coder_env" "vertex_use" {
agent_id = coder_agent.main.id
agent_id = coder_agent.example.id
name = "CLAUDE_CODE_USE_VERTEX"
value = "1"
}
resource "coder_env" "vertex_project_id" {
agent_id = coder_agent.main.id
agent_id = coder_agent.example.id
name = "ANTHROPIC_VERTEX_PROJECT_ID"
value = "your-gcp-project-id"
}
resource "coder_env" "cloud_ml_region" {
agent_id = coder_agent.main.id
agent_id = coder_agent.example.id
name = "CLOUD_ML_REGION"
value = "global"
}
resource "coder_env" "vertex_sa_json" {
agent_id = coder_agent.main.id
agent_id = coder_agent.example.id
name = "VERTEX_SA_JSON"
value = var.vertex_sa_json
}
resource "coder_env" "google_application_credentials" {
agent_id = coder_agent.main.id
agent_id = coder_agent.example.id
name = "GOOGLE_APPLICATION_CREDENTIALS"
value = "/tmp/gcp-sa.json"
}
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.9"
agent_id = coder_agent.main.id
version = "4.2.0"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514"
+47 -153
View File
@@ -39,11 +39,9 @@ interface SetupProps {
agentapiMockScript?: string;
}
const setup = async (
props?: SetupProps,
): Promise<{ id: string; coderEnvVars: Record<string, string> }> => {
const setup = async (props?: SetupProps): Promise<{ id: string }> => {
const projectDir = "/home/coder/project";
const { id, coderEnvVars } = await setupUtil({
const { id } = await setupUtil({
moduleDir: import.meta.dir,
moduleVariables: {
install_claude_code: props?.skipClaudeMock ? "true" : "false",
@@ -63,7 +61,7 @@ const setup = async (
content: await loadTestFile(import.meta.dir, "claude-mock.sh"),
});
}
return { id, coderEnvVars };
return { id };
};
setDefaultTimeout(60 * 1000);
@@ -81,14 +79,14 @@ describe("claude-code", async () => {
test("install-claude-code-version", async () => {
const version_to_install = "1.0.40";
const { id, coderEnvVars } = await setup({
const { id } = await setup({
skipClaudeMock: true,
moduleVariables: {
install_claude_code: "true",
claude_code_version: version_to_install,
},
});
await execModuleScript(id, coderEnvVars);
await execModuleScript(id);
const resp = await execContainer(id, [
"bash",
"-c",
@@ -98,14 +96,14 @@ describe("claude-code", async () => {
});
test("check-latest-claude-code-version-works", async () => {
const { id, coderEnvVars } = await setup({
const { id } = await setup({
skipClaudeMock: true,
skipAgentAPIMock: true,
moduleVariables: {
install_claude_code: "true",
},
});
await execModuleScript(id, coderEnvVars);
await execModuleScript(id);
await expectAgentAPIStarted(id);
});
@@ -135,13 +133,13 @@ describe("claude-code", async () => {
},
},
});
const { id, coderEnvVars } = await setup({
const { id } = await setup({
skipClaudeMock: true,
moduleVariables: {
mcp: mcpConfig,
},
});
await execModuleScript(id, coderEnvVars);
await execModuleScript(id);
const resp = await readFileContainer(id, "/home/coder/.claude.json");
expect(resp).toContain("test-cmd");
@@ -210,17 +208,13 @@ describe("claude-code", async () => {
});
// Create a mock task session file with the hardcoded task session ID
// Note: Claude CLI creates files without "session-" prefix when using --session-id
const taskSessionId = "cd32e253-ca16-4fd3-9825-d837e74ae3c2";
const sessionDir = `/home/coder/.claude/projects/-home-coder-project`;
await execContainer(id, ["mkdir", "-p", sessionDir]);
await execContainer(id, [
"bash",
"-c",
`cat > ${sessionDir}/${taskSessionId}.jsonl << 'SESSIONEOF'
{"sessionId":"${taskSessionId}","message":{"content":"Task"},"timestamp":"2020-01-01T10:00:00.000Z"}
{"type":"assistant","message":{"content":"Response"},"timestamp":"2020-01-01T10:00:05.000Z"}
SESSIONEOF`,
`touch ${sessionDir}/session-${taskSessionId}.jsonl`,
]);
await execModuleScript(id);
@@ -232,10 +226,46 @@ SESSIONEOF`,
]);
expect(startLog.stdout).toContain("--resume");
expect(startLog.stdout).toContain(taskSessionId);
expect(startLog.stdout).toContain("Resuming task session");
expect(startLog.stdout).toContain("Resuming existing task session");
expect(startLog.stdout).toContain("--dangerously-skip-permissions");
});
test("claude-continue-resume-standalone-session", async () => {
const { id } = await setup({
moduleVariables: {
continue: "true",
report_tasks: "false",
ai_prompt: "test prompt",
},
});
const sessionId = "some-random-session-id";
const workdir = "/home/coder/project";
const claudeJson = {
projects: {
[workdir]: {
lastSessionId: sessionId,
},
},
};
await execContainer(id, [
"bash",
"-c",
`echo '${JSON.stringify(claudeJson)}' > /home/coder/.claude.json`,
]);
await execModuleScript(id);
const startLog = await execContainer(id, [
"bash",
"-c",
"cat /home/coder/.claude-module/agentapi-start.log",
]);
expect(startLog.stdout).toContain("--continue");
expect(startLog.stdout).toContain("Resuming existing session");
});
test("pre-post-install-scripts", async () => {
const { id } = await setup({
moduleVariables: {
@@ -330,140 +360,4 @@ SESSIONEOF`,
"ARG_AGENTAPI_CHAT_BASE_PATH=/@default/default.foo/apps/ccw/chat",
);
});
test("partial-initialization-detection", async () => {
const { id } = await setup({
moduleVariables: {
continue: "true",
report_tasks: "true",
ai_prompt: "test prompt",
},
});
const taskSessionId = "cd32e253-ca16-4fd3-9825-d837e74ae3c2";
const sessionDir = `/home/coder/.claude/projects/-home-coder-project`;
await execContainer(id, ["mkdir", "-p", sessionDir]);
await execContainer(id, [
"bash",
"-c",
`echo '{"sessionId":"${taskSessionId}"}' > ${sessionDir}/${taskSessionId}.jsonl`,
]);
await execModuleScript(id);
const startLog = await execContainer(id, [
"bash",
"-c",
"cat /home/coder/.claude-module/agentapi-start.log",
]);
// Should start new session, not try to resume invalid one
expect(startLog.stdout).toContain("Starting new task session");
expect(startLog.stdout).toContain("--session-id");
});
test("standalone-first-build-no-sessions", async () => {
const { id } = await setup({
moduleVariables: {
continue: "true",
report_tasks: "false",
},
});
await execModuleScript(id);
const startLog = await execContainer(id, [
"bash",
"-c",
"cat /home/coder/.claude-module/agentapi-start.log",
]);
// Should start fresh, not try to continue
expect(startLog.stdout).toContain("No sessions found");
expect(startLog.stdout).toContain("starting fresh standalone session");
expect(startLog.stdout).not.toContain("--continue");
});
test("standalone-with-sessions-continues", async () => {
const { id } = await setup({
moduleVariables: {
continue: "true",
report_tasks: "false",
},
});
const sessionDir = `/home/coder/.claude/projects/-home-coder-project`;
await execContainer(id, ["mkdir", "-p", sessionDir]);
await execContainer(id, [
"bash",
"-c",
`cat > ${sessionDir}/generic-123.jsonl << 'EOF'
{"sessionId":"generic-123","message":{"content":"User session"},"timestamp":"2020-01-01T10:00:00.000Z"}
{"type":"assistant","message":{"content":"Response"},"timestamp":"2020-01-01T10:00:05.000Z"}
EOF`,
]);
await execModuleScript(id);
const startLog = await execContainer(id, [
"bash",
"-c",
"cat /home/coder/.claude-module/agentapi-start.log",
]);
// Should continue existing session
expect(startLog.stdout).toContain("Sessions found");
expect(startLog.stdout).toContain(
"Continuing most recent standalone session",
);
expect(startLog.stdout).toContain("--continue");
});
test("task-mode-ignores-manual-sessions", async () => {
const { id } = await setup({
moduleVariables: {
continue: "true",
report_tasks: "true",
ai_prompt: "test prompt",
},
});
const taskSessionId = "cd32e253-ca16-4fd3-9825-d837e74ae3c2";
const sessionDir = `/home/coder/.claude/projects/-home-coder-project`;
await execContainer(id, ["mkdir", "-p", sessionDir]);
// Create task session (without "session-" prefix, as CLI does)
await execContainer(id, [
"bash",
"-c",
`cat > ${sessionDir}/${taskSessionId}.jsonl << 'EOF'
{"sessionId":"${taskSessionId}","message":{"content":"Task"},"timestamp":"2020-01-01T10:00:00.000Z"}
{"type":"assistant","message":{"content":"Response"},"timestamp":"2020-01-01T10:00:05.000Z"}
EOF`,
]);
// Create manual session (newer)
await execContainer(id, [
"bash",
"-c",
`cat > ${sessionDir}/manual-456.jsonl << 'EOF'
{"sessionId":"manual-456","message":{"content":"Manual"},"timestamp":"2020-01-02T10:00:00.000Z"}
{"type":"assistant","message":{"content":"Response"},"timestamp":"2020-01-02T10:00:05.000Z"}
EOF`,
]);
await execModuleScript(id);
const startLog = await execContainer(id, [
"bash",
"-c",
"cat /home/coder/.claude-module/agentapi-start.log",
]);
// Should resume task session, not manual session
expect(startLog.stdout).toContain("Resuming task session");
expect(startLog.stdout).toContain(taskSessionId);
expect(startLog.stdout).not.toContain("manual-456");
});
});
+9 -12
View File
@@ -86,7 +86,7 @@ variable "install_agentapi" {
variable "agentapi_version" {
type = string
description = "The version of AgentAPI to install."
default = "v0.11.6"
default = "v0.10.0"
}
variable "ai_prompt" {
@@ -288,20 +288,15 @@ resource "coder_env" "disable_autoupdater" {
value = "1"
}
resource "coder_env" "claude_binary_path" {
agent_id = var.agent_id
name = "PATH"
value = "$HOME/.local/bin:$PATH"
}
locals {
# we have to trim the slash because otherwise coder exp mcp will
# set up an invalid claude config
workdir = trimsuffix(var.workdir, "/")
app_slug = "ccw"
install_script = file("${path.module}/scripts/install.sh")
start_script = file("${path.module}/scripts/start.sh")
module_dir_name = ".claude-module"
workdir = trimsuffix(var.workdir, "/")
app_slug = "ccw"
install_script = file("${path.module}/scripts/install.sh")
start_script = file("${path.module}/scripts/start.sh")
module_dir_name = ".claude-module"
remove_last_session_id_script_b64 = base64encode(file("${path.module}/scripts/remove-last-session-id.sh"))
# Extract hostname from access_url for boundary --allow flag
coder_host = replace(replace(data.coder_workspace.me.access_url, "https://", ""), "http://", "")
@@ -362,7 +357,9 @@ module "agentapi" {
set -o errexit
set -o pipefail
echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh
echo -n "${local.remove_last_session_id_script_b64}" | base64 -d > "/tmp/remove-last-session-id.sh"
chmod +x /tmp/start.sh
chmod +x /tmp/remove-last-session-id.sh
ARG_MODEL='${var.model}' \
ARG_RESUME_SESSION_ID='${var.resume_session_id}' \

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