From a3a9b0e24b33a5a6ae0d8d4fbcdbaac40a1eeddf Mon Sep 17 00:00:00 2001 From: Barnabas Balogh Date: Sun, 12 Jul 2026 18:06:43 +0200 Subject: [PATCH] git init | first commit --- .env.example | 3 + .gitignore | 12 ++ Dockerfile | 43 ++++++ README.md | 132 +++++++++++++++++- docker-compose.yml | 20 +++ opencode-config/agent/executor.md | 62 +++++++++ opencode-config/command/commit.md | 74 ++++++++++ opencode-config/command/rapid.md | 57 ++++++++ opencode-config/plugin/fail-fast.ts | 156 ++++++++++++++++++++++ opencode-config/snippet/commit-message.md | 7 + opencode-config/snippet/create-plan.md | 7 + 11 files changed, 572 insertions(+), 1 deletion(-) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 opencode-config/agent/executor.md create mode 100644 opencode-config/command/commit.md create mode 100644 opencode-config/command/rapid.md create mode 100644 opencode-config/plugin/fail-fast.ts create mode 100644 opencode-config/snippet/commit-message.md create mode 100644 opencode-config/snippet/create-plan.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..574ad56 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +OPENCHAMBER_PORT=5200 +UI_PASSWORD= +REPOSITORIES_PATH=./repositories diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c7d3955 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..597304f --- /dev/null +++ b/Dockerfile @@ -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}"] diff --git a/README.md b/README.md index 5b59855..850c242 100644 --- a/README.md +++ b/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. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0c69e3a --- /dev/null +++ b/docker-compose.yml @@ -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 diff --git a/opencode-config/agent/executor.md b/opencode-config/agent/executor.md new file mode 100644 index 0000000..0d0c749 --- /dev/null +++ b/opencode-config/agent/executor.md @@ -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. \ No newline at end of file diff --git a/opencode-config/command/commit.md b/opencode-config/command/commit.md new file mode 100644 index 0000000..d8f5799 --- /dev/null +++ b/opencode-config/command/commit.md @@ -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 \ No newline at end of file diff --git a/opencode-config/command/rapid.md b/opencode-config/command/rapid.md new file mode 100644 index 0000000..0a0a8cb --- /dev/null +++ b/opencode-config/command/rapid.md @@ -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. diff --git a/opencode-config/plugin/fail-fast.ts b/opencode-config/plugin/fail-fast.ts new file mode 100644 index 0000000..9cfbb6f --- /dev/null +++ b/opencode-config/plugin/fail-fast.ts @@ -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*(?[\w./-]+):\s*command not found\b/i, + /(?:^|\n)(?[\w./-]+):\s*command not found\b/i, + /(?:^|\n)(?:sh|bash|zsh|fish):\s*(?[\w./-]+):\s*not found\b/i, + /(?:^|\n)(?[\w./-]+):\s*not found\b/i, + /(?:^|\n)env:\s*(?[\w./-]+):\s*No such file or directory\b/i, + /(?:^|\n).*spawn\s+(?[\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 + 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 diff --git a/opencode-config/snippet/commit-message.md b/opencode-config/snippet/commit-message.md new file mode 100644 index 0000000..91265ef --- /dev/null +++ b/opencode-config/snippet/commit-message.md @@ -0,0 +1,7 @@ +--- +aliases: + - cm +description: Creates a commit message +--- + +Based on only the current changes recommend me a commit message. \ No newline at end of file diff --git a/opencode-config/snippet/create-plan.md b/opencode-config/snippet/create-plan.md new file mode 100644 index 0000000..0d673a9 --- /dev/null +++ b/opencode-config/snippet/create-plan.md @@ -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. \ No newline at end of file