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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .icons/plandex.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions registry/fran-mora/.images/avatar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions registry/fran-mora/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
display_name: Francesco Moramarco
bio: AI/ML engineer shipping modules for AI coding agents and developer tooling.
github: fran-mora
avatar: ./.images/avatar.svg
status: community
---

# Francesco Moramarco

AI/ML engineer focused on AI coding agents, developer tooling, and dev-environment ergonomics.

## Modules

- **plandex**: Install and run the [Plandex](https://plandex.ai) CLI AI coding agent in your Coder workspace.

## 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.
157 changes: 157 additions & 0 deletions registry/fran-mora/modules/plandex/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
---
display_name: Plandex
description: Install and configure the Plandex CLI AI coding agent in your workspace.
icon: ../../../../.icons/plandex.svg
verified: false
tags: [agent, plandex, ai, cli]
---

# Plandex

Install and configure the [Plandex](https://plandex.ai) CLI AI coding agent in your workspace. Starting Plandex is left to the caller (template command, IDE launcher, or a custom `coder_script`) — the same pattern used by the official `claude-code` module.

```tf
module "plandex" {
source = "registry.coder.com/fran-mora/plandex/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
openai_api_key = "sk-..."
}
```

## Prerequisites

Plandex needs at least one upstream LLM provider key. Set whichever the user prefers:

- `openai_api_key` — passed to Plandex via `OPENAI_API_KEY`. Default provider.
- `anthropic_api_key` — passed via `ANTHROPIC_API_KEY`.
- `google_api_key` — passed via `GOOGLE_API_KEY`.
- `openrouter_api_key` — passed via `OPENROUTER_API_KEY`.

For a self-hosted Plandex server, also set `plandex_api_host` to the server URL.

## Examples

### Standalone mode with a launcher app

Install Plandex against the user's OpenAI key and add a `coder_app` that opens a Plandex REPL in the workspace from the dashboard.

```tf
locals {
plandex_workdir = "/home/coder/project"
}

module "plandex" {
source = "registry.coder.com/fran-mora/plandex/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
workdir = local.plandex_workdir
openai_api_key = "sk-..."
}

resource "coder_app" "plandex" {
agent_id = coder_agent.main.id
slug = "plandex"
display_name = "Plandex"
icon = "/icon/plandex.svg"
open_in = "slim-window"
command = <<-EOT
#!/bin/bash
set -e
cd ${local.plandex_workdir}
plandex
EOT
}
```

> [!NOTE]
> `coder_app.command` runs when the user clicks the app tile. The module sets the relevant API-key env vars on the agent so the CLI starts pre-authenticated.

### Pin a specific Plandex version

```tf
module "plandex" {
source = "registry.coder.com/fran-mora/plandex/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
plandex_version = "2.2.1"
openai_api_key = "sk-..."
}
```

### Self-hosted Plandex server

Point the CLI at a self-hosted Plandex server instead of Plandex Cloud.

```tf
module "plandex" {
source = "registry.coder.com/fran-mora/plandex/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
plandex_api_host = "https://plandex.example.com"
anthropic_api_key = "sk-ant-..."
}
```

### Skip the installer (Plandex pre-installed in the image)

If Plandex is already baked into the workspace image, set `install_plandex = false` so the module only configures env vars.

```tf
module "plandex" {
source = "registry.coder.com/fran-mora/plandex/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
install_plandex = false
openai_api_key = "sk-..."
}
```

### Serialize a downstream `coder_script` after the install pipeline

The module exposes the `coder exp sync` name of each script it creates via the `scripts` output: an ordered list (`pre_install`, `install`, `post_install`) of names for scripts this module actually creates. Scripts that were not configured are absent from the list.

Downstream `coder_script` resources can wait for this module's install pipeline to finish using `coder exp sync want <self> <each name>`:

```tf
module "plandex" {
source = "registry.coder.com/fran-mora/plandex/coder"
version = "1.0.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
openai_api_key = "sk-..."
}

resource "coder_script" "post_plandex" {
agent_id = coder_agent.main.id
display_name = "Run after Plandex install"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -euo pipefail
trap 'coder exp sync complete post-plandex' EXIT
coder exp sync want post-plandex ${join(" ", module.plandex.scripts)}
coder exp sync start post-plandex

# Your work here runs after plandex finishes installing.
plandex version
EOT
}
```

## Troubleshooting

If Plandex doesn't appear on the workspace `PATH` after install, check the install log:

```bash
cat ~/.coder-modules/fran-mora/plandex/logs/install.log
```

The Plandex installer writes the binary to `/usr/local/bin/plandex` if `sudo` is available, otherwise to `$HOME/.local/bin/plandex`. The module ensures the latter is on `PATH` by appending it to the user's shell profiles.

## References

- [Plandex documentation](https://docs.plandex.ai)
- [Plandex GitHub](https://github.com/plandex-ai/plandex)
155 changes: 155 additions & 0 deletions registry/fran-mora/modules/plandex/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
terraform {
required_version = ">= 1.9"

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 "icon" {
type = string
description = "The icon to use for the app."
default = "/icon/plandex.svg"
}

variable "workdir" {
type = string
description = "Optional project directory. When set, the module pre-creates it if missing so Plandex can be opened in it directly."
default = null
}

variable "pre_install_script" {
type = string
description = "Custom script to run before installing Plandex. Useful for dependency ordering between modules."
default = null
}

variable "post_install_script" {
type = string
description = "Custom script to run after installing Plandex."
default = null
}

variable "install_plandex" {
type = bool
description = "Whether to install Plandex."
default = true
}

variable "plandex_version" {
type = string
description = "The version of Plandex to install. Use 'latest' for the most recent release, or pin a specific version like '2.2.1'."
default = "latest"
}

variable "openai_api_key" {
type = string
description = "OpenAI API key passed to Plandex via the OPENAI_API_KEY env var."
sensitive = true
default = ""
}

variable "anthropic_api_key" {
type = string
description = "Anthropic API key passed to Plandex via the ANTHROPIC_API_KEY env var."
sensitive = true
default = ""
}

variable "google_api_key" {
type = string
description = "Google API key passed to Plandex via the GOOGLE_API_KEY env var."
sensitive = true
default = ""
}

variable "openrouter_api_key" {
type = string
description = "OpenRouter API key passed to Plandex via the OPENROUTER_API_KEY env var."
sensitive = true
default = ""
}

variable "plandex_api_host" {
type = string
description = "Optional Plandex server host. Set this to your self-hosted Plandex server URL (e.g. https://plandex.example.com). Leave empty to use the public Plandex Cloud or BYO-key local mode."
default = ""
}

resource "coder_env" "openai_api_key" {
count = var.openai_api_key != "" ? 1 : 0
agent_id = var.agent_id
name = "OPENAI_API_KEY"
value = var.openai_api_key
}

resource "coder_env" "anthropic_api_key" {
count = var.anthropic_api_key != "" ? 1 : 0
agent_id = var.agent_id
name = "ANTHROPIC_API_KEY"
value = var.anthropic_api_key
}

resource "coder_env" "google_api_key" {
count = var.google_api_key != "" ? 1 : 0
agent_id = var.agent_id
name = "GOOGLE_API_KEY"
value = var.google_api_key
}

resource "coder_env" "openrouter_api_key" {
count = var.openrouter_api_key != "" ? 1 : 0
agent_id = var.agent_id
name = "OPENROUTER_API_KEY"
value = var.openrouter_api_key
}

resource "coder_env" "plandex_api_host" {
count = var.plandex_api_host != "" ? 1 : 0
agent_id = var.agent_id
name = "PLANDEX_API_HOST"
value = var.plandex_api_host
}

locals {
workdir = var.workdir != null ? trimsuffix(var.workdir, "/") : ""
install_script = templatefile("${path.module}/scripts/install.sh.tftpl", {
ARG_PLANDEX_VERSION = var.plandex_version
ARG_INSTALL_PLANDEX = tostring(var.install_plandex)
ARG_WORKDIR = local.workdir
})
module_dir_name = ".coder-modules/fran-mora/plandex"
}

module "coder_utils" {
source = "registry.coder.com/coder/coder-utils/coder"
version = "0.0.1"

agent_id = var.agent_id
module_directory = "$HOME/${local.module_dir_name}"
display_name_prefix = "Plandex"
icon = var.icon
pre_install_script = var.pre_install_script
post_install_script = var.post_install_script
install_script = local.install_script
}

# Pass-through of coder-utils script outputs so upstream modules can serialize
# their coder_script resources behind this module's install pipeline using
# `coder exp sync want <self> <each name>`.
output "scripts" {
description = "Ordered list of coder exp sync names for the coder_script resources this module actually creates, in run order (pre_install, install, post_install). Scripts that were not configured are absent from the list."
value = module.coder_utils.scripts
}
Loading
Loading