git init | first commit
This commit is contained in:
3
.env.example
Normal file
3
.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
OPENCHAMBER_PORT=5200
|
||||
UI_PASSWORD=
|
||||
REPOSITORIES_PATH=./repositories
|
||||
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
.DS_Store
|
||||
.env
|
||||
repositories/
|
||||
opencode-cache/
|
||||
opencode-data/
|
||||
opencode-state/
|
||||
openchamber-config/
|
||||
opencode-config/agents
|
||||
opencode-config/commands
|
||||
opencode-config/skills
|
||||
opencode-config/plugins
|
||||
opencode-config/opencode.jsonc
|
||||
43
Dockerfile
Normal file
43
Dockerfile
Normal file
@@ -0,0 +1,43 @@
|
||||
FROM node:26-trixie-slim
|
||||
|
||||
USER root
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
bash \
|
||||
ca-certificates \
|
||||
coreutils \
|
||||
findutils \
|
||||
git \
|
||||
grep \
|
||||
openssh-client \
|
||||
tar \
|
||||
docker.io \
|
||||
docker-compose \
|
||||
python3 \
|
||||
python3-pip \
|
||||
pipx \
|
||||
make \
|
||||
g++ \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
USER node
|
||||
|
||||
ENV HOME=/home/node
|
||||
ENV PATH="/home/node/.local/bin:/home/node/.opencode/bin:${PATH}"
|
||||
|
||||
RUN npm config set prefix ~/.local
|
||||
|
||||
WORKDIR /home/node
|
||||
|
||||
# Install OpenCode CLI
|
||||
RUN curl -fsSL https://opencode.ai/install | bash
|
||||
|
||||
# Install OpenChamber (web + PWA)
|
||||
RUN curl -fsSL https://raw.githubusercontent.com/openchamber/openchamber/main/scripts/install.sh | bash
|
||||
|
||||
# Install Graphifyy
|
||||
RUN pipx install graphifyy && graphify install opencode
|
||||
|
||||
CMD ["sh", "-c", "rm -f /home/node/.config/openchamber/run/openchamber-*.pid /home/node/.config/openchamber/run/openchamber-*.json && exec openchamber serve --host 0.0.0.0 --port ${OPENCHAMBER_PORT:-5200} --foreground ${UI_PASSWORD:+--ui-password $UI_PASSWORD}"]
|
||||
132
README.md
132
README.md
@@ -1,2 +1,132 @@
|
||||
# my-opencode-simple
|
||||
# OpenCode Web
|
||||
|
||||
Dockerized [OpenCode](https://opencode.ai) with [OpenChamber](https://github.com/openchamber/openchamber) as the browser/PWA frontend.
|
||||
|
||||
## What it does
|
||||
|
||||
This project runs OpenChamber in the container and lets OpenChamber start and keep OpenCode available in the background.
|
||||
|
||||
The stack inside the container:
|
||||
|
||||
- **OpenCode CLI** — the AI coding assistant
|
||||
- **OpenChamber** — web frontend that starts and presents OpenCode
|
||||
- **Node 26** — runtime
|
||||
- **Graphifyy** — knowledge-graph skill for codebase understanding (installed via pipx)
|
||||
|
||||
## OpenCode configuration
|
||||
|
||||
The `opencode-config/` directory contains a full custom OpenCode agent setup, mounted at `/home/node/.config/opencode` inside the container.
|
||||
|
||||
### Agents
|
||||
|
||||
| File | Mode | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `agents/build.md` | primary | Default agent with full permission set for implementation work |
|
||||
| `agents/explore.md` | subagent | Specialized codebase file/content search |
|
||||
| `agents/general.md` | subagent | General-purpose multi-step research and execution |
|
||||
| `agents/plan.md` | primary | Plan mode — architecture design and implementation plans (read-only) |
|
||||
| `agent/executor.md` | subagent | Runs shell commands only, no editing or file-reading |
|
||||
|
||||
### Commands
|
||||
|
||||
| File | Description |
|
||||
| --- | --- |
|
||||
| `command/commit.md` | Create git commits with auto-generated conventional commit messages |
|
||||
| `command/rapid.md` | Fast iteration mode — quick fixes without extensive planning |
|
||||
| `commands/plan.md` | Creates detailed implementation plans |
|
||||
| `commands/review.md` | Reviews code changes (commit, branch, PR, or uncommitted) |
|
||||
|
||||
### Plugins
|
||||
|
||||
| File | Description |
|
||||
| --- | --- |
|
||||
| `plugin/fail-fast.ts` | Converts denied permissions and missing shell utilities into explicit task failures so agents don't stall or retry blocked actions |
|
||||
|
||||
### Snippets
|
||||
|
||||
| File | Aliases | Description |
|
||||
| --- | --- | --- |
|
||||
| `snippet/commit-message.md` | `cm` | Generates a commit message from current changes |
|
||||
| `snippet/create-plan.md` | `plan` | Creates an implementation plan from the current session |
|
||||
|
||||
### Permissions
|
||||
|
||||
Fine-grained tool permissions are defined in `opencode.jsonc` (auto-generated, gitignored). Each agent has a tailored permission set — for example, `explore` has broad read/search access with bash allowed, while `build` has write/edit access but denies bash for safety.
|
||||
|
||||
## Fail-fast plugin
|
||||
|
||||
The fail-fast plugin (`plugin/fail-fast.ts`) hooks into OpenCode's tool lifecycle to catch three conditions:
|
||||
|
||||
1. **Permission denied** — if a `permission.ask` results in `deny`, the plugin throws a `FAIL_FAST_PERMISSION_DENIED` error with the permission name and next-action guidance.
|
||||
2. **Invalid command** — if bash is called without a command, it throws `FAIL_FAST_INVALID_COMMAND`.
|
||||
3. **Missing utility** — after every bash execution, the plugin checks stderr/stdout for "command not found", "not found", "ENOENT" patterns. If found, it throws `FAIL_FAST_MISSING_UTILITY` with the utility name.
|
||||
|
||||
This prevents agents from silently retrying blocked operations or looping on missing dependencies.
|
||||
|
||||
## How to run
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Open `http://localhost:5200` in your browser.
|
||||
|
||||
To run OpenChamber on a different port, set `OPENCHAMBER_PORT` in `.env` and restart the container:
|
||||
|
||||
```env
|
||||
OPENCHAMBER_PORT=5300
|
||||
```
|
||||
|
||||
The container uses host networking, so OpenChamber binds directly to `OPENCHAMBER_PORT`; there is no separate Docker port mapping to update.
|
||||
|
||||
To protect the UI, set `UI_PASSWORD` in `.env`.
|
||||
|
||||
User and group IDs can be set in `.env` to match the host user:
|
||||
|
||||
```env
|
||||
UID=1000
|
||||
GID=1000
|
||||
```
|
||||
|
||||
## How to update
|
||||
|
||||
Rebuild the image to pull the latest versions of OpenCode and OpenChamber:
|
||||
|
||||
```bash
|
||||
docker compose build --no-cache
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Managing repositories
|
||||
|
||||
Set `REPOSITORIES_PATH` in `.env` to the host directory that contains repositories OpenCode should work on:
|
||||
|
||||
```env
|
||||
REPOSITORIES_PATH=./repositories
|
||||
```
|
||||
|
||||
The directory is mounted at `/home/repositories` inside the container.
|
||||
|
||||
## Container details
|
||||
|
||||
| Aspect | Detail |
|
||||
| --- | --- |
|
||||
| Network | `host` mode — no port mapping needed |
|
||||
| Docker access | `/var/run/docker.sock` mounted for in-container Docker usage |
|
||||
| Container user | Runs as `${UID}:${GID}` (default 1000:1000) |
|
||||
| OpenChamber | Configured to allow unauthenticated LAN access (`OPENCHAMBER_ALLOW_UNAUTHENTICATED_LAN=true`) |
|
||||
|
||||
## Persisted data (gitignored)
|
||||
|
||||
| Local path | Container mount point | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `.config/opencode-data/` | `/home/node/.local/share/opencode` | Agent state and tool outputs |
|
||||
| `.config/opencode-state/` | `/home/node/.local/state/opencode` | Session and runtime state |
|
||||
| `.config/opencode-cache/` | `/home/node/.cache/opencode` | Model cache and temporary data |
|
||||
| `.config/openchamber-config/` | `/home/node/.config/openchamber` | OpenChamber configuration and session data |
|
||||
|
||||
## Browser and PWA notifications
|
||||
|
||||
OpenChamber can send browser notifications for prompt completion, questions, errors, and subtasks.
|
||||
|
||||
To receive notifications, open OpenChamber in your browser or installed PWA and allow notifications for the site/app when prompted. If notifications were previously blocked, reset the site notification permission in your browser settings.
|
||||
|
||||
20
docker-compose.yml
Normal file
20
docker-compose.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
services:
|
||||
my-opencode:
|
||||
user: "${UID}:${GID}"
|
||||
build:
|
||||
context: .
|
||||
volumes:
|
||||
- "./opencode-config:/home/node/.config/opencode"
|
||||
- ".config/opencode-data:/home/node/.local/share/opencode"
|
||||
- ".config/opencode-state:/home/node/.local/state/opencode"
|
||||
- ".config/opencode-cache:/home/node/.cache/opencode"
|
||||
- ".config/openchamber-config:/home/node/.config/openchamber"
|
||||
- "${REPOSITORIES_PATH:-./repositories}:/home/node/repositories"
|
||||
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||
environment:
|
||||
OPENCHAMBER_PORT: ${OPENCHAMBER_PORT:-5200}
|
||||
OPENCODE_BINARY: /home/node/.opencode/bin/opencode
|
||||
OPENCHAMBER_ALLOW_UNAUTHENTICATED_LAN: true
|
||||
UI_PASSWORD: ${UI_PASSWORD:-}
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
62
opencode-config/agent/executor.md
Normal file
62
opencode-config/agent/executor.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
description: Runs shell commands and returns command results. Use this instead
|
||||
of the general agent whenever a task is only to execute commands.
|
||||
mode: subagent
|
||||
temperature: 0.1
|
||||
tools:
|
||||
write: false
|
||||
edit: false
|
||||
bash: true
|
||||
permission:
|
||||
doom_loop: ask
|
||||
external_directory:
|
||||
"*": ask
|
||||
/home/.local/share/opencode/tool-output/*: allow
|
||||
/tmp/opencode/*: allow
|
||||
question: deny
|
||||
plan_enter: deny
|
||||
plan_exit: deny
|
||||
read:
|
||||
"*": deny
|
||||
"*.env": ask
|
||||
"*.env.*": ask
|
||||
"*.env.example": allow
|
||||
edit: deny
|
||||
bash:
|
||||
rm *: deny
|
||||
git push*: deny
|
||||
git reset --hard*: deny
|
||||
glob: deny
|
||||
grep: deny
|
||||
list: deny
|
||||
task: deny
|
||||
todowrite: deny
|
||||
webfetch: deny
|
||||
websearch: deny
|
||||
model: openai/gpt-5.4-mini
|
||||
---
|
||||
|
||||
You are the executor agent.
|
||||
|
||||
Your only responsibility is to run shell commands requested by the caller and report the results. Do not inspect files with non-shell tools, edit files, research, plan, or perform general coding work.
|
||||
|
||||
## Fail-Fast Capability Check
|
||||
|
||||
Before running a command, check whether it requires a denied permission, destructive action, or unavailable shell utility. If a command fails because a utility is missing or permission is denied, do not retry variants blindly.
|
||||
|
||||
Stop immediately and report:
|
||||
|
||||
- `FAIL_FAST_CAPABILITY_MISSING`
|
||||
- Missing utility, denied permission, or blocked command
|
||||
- Why it is required
|
||||
- The exact command attempted or requested
|
||||
- Exact next action for the caller
|
||||
|
||||
For each requested command:
|
||||
|
||||
1. Run the command exactly as needed, using the shell tool.
|
||||
2. Report whether it succeeded or failed.
|
||||
3. Include the relevant stdout, stderr, and exit status when available.
|
||||
4. Keep analysis minimal and limited to what the command output shows.
|
||||
|
||||
If the request is not primarily command execution, say that it should be handled by another agent.
|
||||
74
opencode-config/command/commit.md
Normal file
74
opencode-config/command/commit.md
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
description: Create git commit with auto-generated conventional commit message
|
||||
agent: build
|
||||
model: opencode/deepseek-v4-flash-free
|
||||
---
|
||||
|
||||
# Git Commit Command
|
||||
|
||||
Create a well-structured git commit by analyzing staged changes and generating a conventional commit message.
|
||||
|
||||
## Phase 1: Gather Context
|
||||
|
||||
Run these commands in parallel to understand the current state:
|
||||
|
||||
1. `git status` - See all staged and unstaged changes
|
||||
2. `git diff --cached` - View the actual staged changes that will be committed
|
||||
3. `git log --oneline -5` - Review recent commit message style for consistency
|
||||
|
||||
## Phase 2: Analyze Changes
|
||||
|
||||
Based on the staged diff, determine:
|
||||
|
||||
1. **Change Type** (use conventional commits):
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `refactor`: Code restructuring without behavior change
|
||||
- `docs`: Documentation only
|
||||
- `test`: Adding or updating tests
|
||||
- `chore`: Maintenance tasks
|
||||
- `style`: Formatting, whitespace
|
||||
- `perf`: Performance improvement
|
||||
- `ci`: CI/CD changes
|
||||
|
||||
2. **Scope** (optional): Affected module or component
|
||||
|
||||
3. **Description**: Concise summary focusing on WHY, not WHAT
|
||||
|
||||
## Phase 3: Generate Commit Message
|
||||
|
||||
Format: `type(scope): description`
|
||||
|
||||
Rules:
|
||||
- Use imperative mood ("Add feature" not "Added feature")
|
||||
- Keep first line under 72 characters
|
||||
- Focus on the purpose and impact
|
||||
- Add body for complex changes explaining reasoning
|
||||
|
||||
## Phase 4: Execute Commit
|
||||
|
||||
1. If there are unstaged changes that should be included, ask user first
|
||||
2. Stage any additional files if requested
|
||||
3. Execute the commit with the generated message
|
||||
4. Run `git status` to verify success
|
||||
|
||||
## Output Format
|
||||
|
||||
```
|
||||
Commit Analysis
|
||||
---------------
|
||||
Type: [type]
|
||||
Scope: [scope or "none"]
|
||||
Files: [number] files changed
|
||||
|
||||
Generated Message:
|
||||
[commit message]
|
||||
|
||||
Commit Status: [success/failure]
|
||||
```
|
||||
|
||||
## Safety Checks
|
||||
|
||||
- NEVER commit files that appear to contain secrets (.env, credentials, API keys)
|
||||
- WARN if committing lock files or large binary files
|
||||
- CONFIRM before committing if there are untracked files that might be forgotten
|
||||
57
opencode-config/command/rapid.md
Normal file
57
opencode-config/command/rapid.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
description: Fast iteration mode - quick fixes without extensive planning
|
||||
agent: build
|
||||
subtask: false
|
||||
---
|
||||
# Rapid Mode - Fast Iteration
|
||||
|
||||
You are in **rapid mode**. Optimize for speed and iteration velocity. Skip extensive planning and get to implementation quickly.
|
||||
|
||||
## Your Mission
|
||||
|
||||
Execute the following task with minimal ceremony:
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
## Rapid Execution Protocol
|
||||
|
||||
1. **Understand** (30 seconds max)
|
||||
- What exactly needs to change?
|
||||
- What's the fastest path to working code?
|
||||
|
||||
2. **Execute** (immediately)
|
||||
- Make the change directly
|
||||
- Use existing patterns - don't reinvent
|
||||
- Keep changes minimal and focused
|
||||
|
||||
3. **Verify** (quick check)
|
||||
- Does it work? Test it.
|
||||
- Any obvious issues? Fix them.
|
||||
|
||||
## Rapid Mode Rules
|
||||
|
||||
- **No extensive planning** - Act first, refine later
|
||||
- **Minimal changes** - Touch only what's necessary
|
||||
- **Use existing patterns** - Copy from similar code in the codebase
|
||||
- **Skip documentation** - Unless explicitly requested
|
||||
- **Iterate fast** - Ship something, then improve
|
||||
- **Ask only if blocked** - Make reasonable assumptions otherwise
|
||||
|
||||
## What Rapid Mode is NOT
|
||||
|
||||
- Not for complex architectural changes (use `/architect`)
|
||||
- Not for learning or understanding (use `/mentor`)
|
||||
- Not for critical security code (use `/review`)
|
||||
- Not for large refactors (use `/refactor`)
|
||||
|
||||
## Output Style
|
||||
|
||||
Keep responses concise:
|
||||
|
||||
```
|
||||
Changed: [what was modified]
|
||||
Verified: [how it was tested]
|
||||
Note: [any caveats, if critical]
|
||||
```
|
||||
|
||||
Do not explain decisions unless they're non-obvious. Just ship it.
|
||||
156
opencode-config/plugin/fail-fast.ts
Normal file
156
opencode-config/plugin/fail-fast.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Fail Fast Plugin
|
||||
* Converts denied permissions and missing shell utilities into explicit task
|
||||
* failures so agents do not repeatedly retry or stall on unavailable actions.
|
||||
*/
|
||||
|
||||
import type { Plugin } from "@opencode-ai/plugin"
|
||||
|
||||
const MISSING_UTILITY_PATTERNS = [
|
||||
/(?:^|\n)(?:sh|bash|zsh|fish):\s*(?<command>[\w./-]+):\s*command not found\b/i,
|
||||
/(?:^|\n)(?<command>[\w./-]+):\s*command not found\b/i,
|
||||
/(?:^|\n)(?:sh|bash|zsh|fish):\s*(?<command>[\w./-]+):\s*not found\b/i,
|
||||
/(?:^|\n)(?<command>[\w./-]+):\s*not found\b/i,
|
||||
/(?:^|\n)env:\s*(?<command>[\w./-]+):\s*No such file or directory\b/i,
|
||||
/(?:^|\n).*spawn\s+(?<command>[\w./-]+)\s+ENOENT\b/i,
|
||||
]
|
||||
|
||||
const SHELL_BUILTINS = new Set([
|
||||
"alias",
|
||||
"break",
|
||||
"cd",
|
||||
"command",
|
||||
"continue",
|
||||
"dirs",
|
||||
"echo",
|
||||
"eval",
|
||||
"exec",
|
||||
"exit",
|
||||
"export",
|
||||
"false",
|
||||
"fg",
|
||||
"hash",
|
||||
"jobs",
|
||||
"popd",
|
||||
"printf",
|
||||
"pushd",
|
||||
"pwd",
|
||||
"read",
|
||||
"return",
|
||||
"set",
|
||||
"shift",
|
||||
"source",
|
||||
"test",
|
||||
"true",
|
||||
"type",
|
||||
"ulimit",
|
||||
"unalias",
|
||||
"unset",
|
||||
])
|
||||
|
||||
function normalizeCommand(command: string): string {
|
||||
return command.replace(/^['"]|['"]$/g, "").trim()
|
||||
}
|
||||
|
||||
function findMissingUtility(output: string): string | undefined {
|
||||
for (const pattern of MISSING_UTILITY_PATTERNS) {
|
||||
const match = pattern.exec(output)
|
||||
const command = match?.groups?.command
|
||||
|
||||
if (!command) {
|
||||
continue
|
||||
}
|
||||
|
||||
const normalized = normalizeCommand(command)
|
||||
|
||||
if (normalized && !SHELL_BUILTINS.has(normalized)) {
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getBashCommand(args: unknown): string | undefined {
|
||||
if (!args || typeof args !== "object") {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const record = args as Record<string, unknown>
|
||||
const command = record.command ?? record.cmd ?? record.script
|
||||
|
||||
return typeof command === "string" ? command : undefined
|
||||
}
|
||||
|
||||
function describePermission(input: {
|
||||
type: string
|
||||
pattern?: string | string[]
|
||||
title: string
|
||||
}): string {
|
||||
const pattern = Array.isArray(input.pattern)
|
||||
? input.pattern.join(", ")
|
||||
: input.pattern
|
||||
|
||||
return [input.type, pattern].filter(Boolean).join(" ") || input.title
|
||||
}
|
||||
|
||||
export const FailFastPlugin = (async () => {
|
||||
return {
|
||||
"permission.ask": async (input, output) => {
|
||||
if (output.status !== "deny") {
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
[
|
||||
"FAIL_FAST_PERMISSION_DENIED: A required tool permission was denied.",
|
||||
`Permission: ${describePermission(input)}`,
|
||||
`Title: ${input.title}`,
|
||||
"Next action: reroute to an agent with the required permission, request permission from the user, or fail the task explicitly.",
|
||||
].join("\n")
|
||||
)
|
||||
},
|
||||
|
||||
"tool.execute.before": async (input, output) => {
|
||||
if (input.tool !== "bash") {
|
||||
return
|
||||
}
|
||||
|
||||
const command = getBashCommand(output.args)
|
||||
|
||||
if (command === undefined || command.trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"FAIL_FAST_INVALID_COMMAND: The bash tool was called without a command. Stop and report the missing command instead of retrying."
|
||||
)
|
||||
},
|
||||
|
||||
"tool.execute.after": async (input, output) => {
|
||||
if (input.tool !== "bash") {
|
||||
return
|
||||
}
|
||||
|
||||
const missingUtility = findMissingUtility(output.output ?? "")
|
||||
|
||||
if (!missingUtility) {
|
||||
return
|
||||
}
|
||||
|
||||
const command = getBashCommand(input.args)
|
||||
|
||||
const commandLine = command ? `\nCommand: ${command}` : ""
|
||||
|
||||
throw new Error(
|
||||
[
|
||||
`FAIL_FAST_MISSING_UTILITY: Required shell utility "${missingUtility}" is not available in this environment.`,
|
||||
commandLine.trim(),
|
||||
"Next action: install or mount the missing utility, choose a different validation path, or fail the task explicitly.",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n")
|
||||
)
|
||||
},
|
||||
}
|
||||
}) satisfies Plugin
|
||||
7
opencode-config/snippet/commit-message.md
Normal file
7
opencode-config/snippet/commit-message.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
aliases:
|
||||
- cm
|
||||
description: Creates a commit message
|
||||
---
|
||||
|
||||
Based on only the current changes recommend me a commit message.
|
||||
7
opencode-config/snippet/create-plan.md
Normal file
7
opencode-config/snippet/create-plan.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
aliases:
|
||||
- plan
|
||||
description: It creates and implementation plan from the previous interactions and chat
|
||||
---
|
||||
|
||||
I want you to create an implementation plan based on our conversation in this session. It should contain a precise instructions so even someone with less knowledge of the system can implement the plan without going into the wrong direction.
|
||||
Reference in New Issue
Block a user