diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 2d1aa9ffc..90ff635da 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -1,18 +1,12 @@ name: ๐Ÿณ Build & Push Docker Image on: - workflow_call: - inputs: - tag: - description: "Release tag" - required: true - type: string + release: + types: [published] env: - GHCR_REGISTRY: ghcr.io - GHCR_IMAGE_NAME: ${{ github.repository_owner }}/picoclaw - DOCKERHUB_REGISTRY: docker.io - DOCKERHUB_IMAGE_NAME: ${{ vars.DOCKERHUB_REPOSITORY }} + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository_owner }}/picoclaw jobs: build: @@ -26,8 +20,6 @@ jobs: # โ”€โ”€ Checkout โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - name: ๐Ÿ“ฅ Checkout repository uses: actions/checkout@v4 - with: - ref: ${{ inputs.tag }} # โ”€โ”€ Docker Buildx โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - name: ๐Ÿ”ง Set up Docker Buildx @@ -35,42 +27,36 @@ jobs: # โ”€โ”€ Login to GHCR โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - name: ๐Ÿ”‘ Login to GitHub Container Registry + if: github.event_name != 'pull_request' uses: docker/login-action@v3 with: - registry: ${{ env.GHCR_REGISTRY }} + registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - # โ”€โ”€ Login to Docker Hub โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - - name: ๐Ÿ”‘ Login to Docker Hub - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKERHUB_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - # โ”€โ”€ Metadata (tags & labels) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - - name: ๐Ÿท๏ธ Prepare image tags - id: tags - shell: bash - run: | - tag="${{ inputs.tag }}" - echo "ghcr_tag=${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:${tag}" >> "$GITHUB_OUTPUT" - echo "ghcr_latest=${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:latest" >> "$GITHUB_OUTPUT" - echo "dockerhub_tag=${{ env.DOCKERHUB_REGISTRY }}/${{ env.DOCKERHUB_IMAGE_NAME }}:${tag}" >> "$GITHUB_OUTPUT" - echo "dockerhub_latest=${{ env.DOCKERHUB_REGISTRY }}/${{ env.DOCKERHUB_IMAGE_NAME }}:latest" >> "$GITHUB_OUTPUT" + - name: ๐Ÿท๏ธ Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,prefix= + type=raw,value=latest,enable={{is_default_branch}} + type=raw,value={{date 'YYYYMMDD-HHmmss'}},enable={{is_default_branch}} # โ”€โ”€ Build & Push โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - name: ๐Ÿš€ Build and push Docker image uses: docker/build-push-action@v6 with: context: . - push: true - tags: | - ${{ steps.tags.outputs.ghcr_tag }} - ${{ steps.tags.outputs.ghcr_latest }} - ${{ steps.tags.outputs.dockerhub_tag }} - ${{ steps.tags.outputs.dockerhub_latest }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - platforms: linux/amd64,linux/arm64,linux/riscv64 + platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f9987b35f..59cc6caeb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,18 +38,14 @@ jobs: git tag -a "${{ inputs.tag }}" -m "Release ${{ inputs.tag }}" git push origin "${{ inputs.tag }}" - release: - name: GoReleaser Release + build-binaries: + name: Build Release Binaries needs: create-tag runs-on: ubuntu-latest - permissions: - contents: write - packages: write steps: - name: Checkout tag uses: actions/checkout@v4 with: - fetch-depth: 0 ref: ${{ inputs.tag }} - name: Setup Go from go.mod @@ -57,42 +53,47 @@ jobs: with: go-version-file: go.mod - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - name: Build all binaries + run: make build-all - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Generate checksums + shell: bash + run: | + shasum -a 256 build/picoclaw-* > build/sha256sums.txt - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + - name: Upload release binaries artifact + uses: actions/upload-artifact@v4 with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + name: picoclaw-binaries + path: | + build/picoclaw-* + build/sha256sums.txt + if-no-files-found: error - - name: Login to Docker Hub - uses: docker/login-action@v3 + create-release: + name: Create GitHub Release + needs: [create-tag, build-binaries] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 with: - registry: docker.io - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + path: release-artifacts - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 + - name: Show downloaded files + run: ls -R release-artifacts + + - name: Create release + uses: softprops/action-gh-release@v2 with: - distribution: goreleaser - version: ~> v2 - args: release --clean + tag_name: ${{ inputs.tag }} + name: ${{ inputs.tag }} + draft: ${{ inputs.draft }} + prerelease: ${{ inputs.prerelease }} + files: | + release-artifacts/**/* + generate_release_notes: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }} - DOCKERHUB_IMAGE_NAME: ${{ vars.DOCKERHUB_REPOSITORY }} - - - name: Apply release flags - shell: bash - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release edit "${{ inputs.tag }}" \ - --draft=${{ inputs.draft }} \ - --prerelease=${{ inputs.prerelease }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 368a0f06b..a2c158331 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -5,11 +5,9 @@ version: 2 before: hooks: - go mod tidy - - go generate ./cmd/picoclaw builds: - - id: picoclaw - env: + - env: - CGO_ENABLED=0 goos: - linux @@ -28,22 +26,6 @@ builds: - goos: windows goarch: arm -dockers_v2: - - id: picoclaw - dockerfile: Dockerfile.goreleaser - ids: - - picoclaw - images: - - "ghcr.io/{{ .Env.GITHUB_REPOSITORY_OWNER }}/picoclaw" - - "docker.io/{{ .Env.DOCKERHUB_IMAGE_NAME }}" - tags: - - "{{ .Tag }}" - - "latest" - platforms: - - linux/amd64 - - linux/arm64 - - linux/riscv64 - archives: - formats: [tar.gz] # this name template makes the OS and Arch compatible with the results of `uname`. @@ -66,10 +48,10 @@ changelog: - "^docs:" - "^test:" -# upx: -# - enabled: true -# compress: best -# lzma: true +upx: + - enabled: true + compress: best + lzma: true release: footer: >- diff --git a/COOLIFY.md b/COOLIFY.md deleted file mode 100644 index 4f8b78ddb..000000000 --- a/COOLIFY.md +++ /dev/null @@ -1,326 +0,0 @@ -# โ˜๏ธ Deploying PicoClaw on Coolify - -Deploy PicoClaw as a self-hosted AI assistant on [Coolify](https://coolify.io) โ€” the open-source Heroku/Vercel alternative. - -> **Looking for the quick 3-step version?** See [README.md โ†’ Deploy on Coolify](README.md#%EF%B8%8F-deploy-on-coolify-3-steps) - ---- - -## ๐Ÿ“‹ Prerequisites - -- A Coolify instance (v4+) -- A GitHub account (to fork the repo) -- At least one LLM API key (e.g., [Gemini](https://aistudio.google.com/apikey), [OpenRouter](https://openrouter.ai/keys)) - -## ๐Ÿš€ Quick Deploy - -### Step 1: Fork the Repository - -Fork [mrbeandev/picoclaw](https://github.com/mrbeandev/picoclaw) to your GitHub account. - -### Step 2: Create a New Service in Coolify - -1. Go to your Coolify dashboard โ†’ **Projects** โ†’ select or create a project -2. Click **+ New** โ†’ **Docker Compose** -3. Connect your forked GitHub repo -4. Set the following: - - **Branch:** `deploy/coolify` - - **Docker Compose File:** `docker-compose-coolify.yml` - - **Base Directory:** `/` (root) - -### Step 3: Configure Environment Variables - -Go to **Environment Variables** tab and add your keys. See [Configuration](#-configuration) below. - -### Step 4: Deploy! - -Hit **Deploy** and wait for the build to complete (~30 seconds). - ---- - -## โš™๏ธ Configuration - -PicoClaw on Coolify supports **3 configuration methods**. The entrypoint script checks them in order โ€” **first match wins**. - -### Which Method Should I Use? - -| | Method 1: JSON Env Var | Method 2: Mounted File | Method 3: Individual Env Vars | -|---|---|---|---| -| **Difficulty** | Medium | Easy | Easiest | -| **Flexibility** | โœ… Full control | โœ… Full control | โš ๏ธ Limited | -| **Custom providers (Ollama, vLLM)** | โœ… Yes | โœ… Yes | โŒ No | -| **Allow-lists** | โœ… Yes | โœ… Yes | โœ… Yes (comma-separated) | -| **Feishu, DingTalk, QQ, OneBot** | โœ… Yes | โœ… Yes | โŒ No | -| **Custom API base URLs** | โœ… Yes | โœ… Yes | โŒ No | -| **Requires JSON minification** | โš ๏ธ Yes | โŒ No | โŒ No | -| **Edit without rebuild** | โœ… Redeploy only | โœ… Restart only | โœ… Redeploy only | -| **Pretty-printed JSON** | โŒ Must minify | โœ… Yes | N/A | - ---- - -### Method 1: Full JSON Config (Most Flexible) โญ - -**Best for:** Full control, custom providers (Ollama, vLLM), complex setups. - -Paste your **entire** `config.json` as a single environment variable: - -| Key | Value | -|-----|-------| -| `PICOCLAW_CONFIG_JSON` | `{"agents":{"defaults":{"provider":"gemini",...}},...}` | - -#### Example: Gemini + Telegram + Ollama - -```json -{ - "agents": { - "defaults": { - "provider": "gemini", - "model": "gemini-2.5-flash-lite", - "max_tokens": 8192, - "temperature": 0.7, - "max_tool_iterations": 20, - "workspace": "~/.picoclaw/workspace", - "restrict_to_workspace": true - } - }, - "providers": { - "gemini": { - "api_key": "AIzaSy..." - }, - "vllm": { - "api_key": "dummy", - "api_base": "http://your-ollama-server:11434/v1" - }, - "openrouter": { - "api_key": "sk-or-..." - } - }, - "channels": { - "telegram": { - "enabled": true, - "token": "123456:ABC-DEF...", - "allow_from": ["your_telegram_user_id"] - }, - "discord": { - "enabled": false, - "token": "", - "allow_from": [] - } - }, - "tools": { - "web": { - "duckduckgo": { "enabled": true, "max_results": 5 }, - "brave": { "enabled": false, "api_key": "", "max_results": 5 } - }, - "firecrawl": { "enabled": false, "api_key": "", "api_base": "https://api.firecrawl.dev/v1" }, - "serpapi": { "enabled": false, "api_key": "", "max_results": 10 } - }, - "heartbeat": { "enabled": true, "interval": 30 }, - "gateway": { "host": "0.0.0.0", "port": 18790 }, - "devices": { "enabled": false, "monitor_usb": false } -} -``` - -#### โš ๏ธ Important: You MUST minify the JSON! - -Coolify environment variables are single-line. You need to compress the JSON into **one line** before pasting. - -**๐Ÿ”ง JSON Tools for Minifying:** - -| Tool | Type | URL | -|------|------|-----| -| **JSON Minifier** | Web | [jsonformatter.org/json-minify](https://jsonformatter.org/json-minify) | -| **JSON Formatter** | Web | [jsonformatter.curiousconcept.com](https://jsonformatter.curiousconcept.com/) | -| **JSON Editor Online** | Web | [jsoneditoronline.org](https://jsoneditoronline.org/) โ€” edit visually, then copy minified | -| **jq** | CLI | `cat config.json \| jq -c .` โ€” outputs minified JSON | -| **Python** | CLI | `python3 -c "import json,sys;print(json.dumps(json.load(sys.stdin)))" < config.json` | -| **Node.js** | CLI | `node -e "process.stdin.on('data',d=>console.log(JSON.stringify(JSON.parse(d))))"< config.json` | - -**Workflow:** -1. Write your config in a pretty-printed JSON editor -2. Validate it (the tools above show errors) -3. Minify / compress to one line -4. Paste the single line as the `PICOCLAW_CONFIG_JSON` value in Coolify - -**Example minified output:** -``` -{"agents":{"defaults":{"provider":"gemini","model":"gemini-2.5-flash-lite","max_tokens":8192,"temperature":0.7,"max_tool_iterations":20,"workspace":"~/.picoclaw/workspace","restrict_to_workspace":true}},"providers":{"gemini":{"api_key":"AIzaSy..."},"vllm":{"api_key":"dummy","api_base":"http://ollama:11434/v1"}},"channels":{"telegram":{"enabled":true,"token":"123456:ABC-DEF...","allow_from":["123456789"]}},"tools":{"web":{"duckduckgo":{"enabled":true,"max_results":5}}},"heartbeat":{"enabled":true,"interval":30},"gateway":{"host":"0.0.0.0","port":18790},"devices":{"enabled":false,"monitor_usb":false}} -``` - ---- - -### Method 2: Mounted Config File - -**Best for:** Users who prefer editing a normal file, and want pretty-printed JSON without minification. - -Use Coolify's **Storages** feature to mount a config file into the container: - -#### Step-by-step: - -1. Go to your PicoClaw service in Coolify -2. Click the **Storages** tab -3. Click **+ Add** and configure: - - **Source Path:** Leave empty (Coolify auto-creates it) or set to `/data/coolify/applications//config.json` - - **Destination Path:** `/config/config.json` -4. Save the storage mount -5. SSH into your Coolify server and create the config file: - ```bash - # Find your app's data directory - ls /data/coolify/applications/ - - # Create the config file (replace with your app's UUID) - nano /data/coolify/applications//config.json - ``` -6. Paste your full config JSON (pretty-printed is fine!) and save -7. **Restart** the service in Coolify (no rebuild needed) - -The entrypoint will automatically detect `/config/config.json` and use it. - -#### โœ… Advantages -- **Pretty-printed JSON** โ€” no minification needed, easy to read and edit -- **Full control** โ€” same flexibility as Method 1 -- **Edit without rebuild** โ€” just edit the file on disk and restart the container -- **Custom providers** โ€” Ollama, vLLM, and any other custom provider work fine - -#### โŒ Limitations -- **Requires SSH access** โ€” you need SSH into the Coolify server to create/edit the file -- **No Coolify UI editing** โ€” you can't edit the file content from Coolify's web UI (only set the mount path) -- **File must exist before starting** โ€” if the file doesn't exist, this method is skipped and it falls through to Method 3 -- **Not portable** โ€” the config lives on the server's filesystem, not in Coolify's database - ---- - -### Method 3: Individual Environment Variables - -**Best for:** Simple setups โ€” just Gemini + one channel, defaults for everything else. - -Set these in Coolify's **Environment Variables** tab: - -#### Required - -| Variable | Description | Example | -|----------|-------------|---------| -| `PICOCLAW_PROVIDERS_GEMINI_API_KEY` | Gemini API key | `AIzaSy...` | - -#### Provider Keys (optional) - -| Variable | Description | -|----------|-------------| -| `PICOCLAW_PROVIDERS_OPENROUTER_API_KEY` | OpenRouter API key | -| `PICOCLAW_PROVIDERS_OPENAI_API_KEY` | OpenAI API key | -| `PICOCLAW_PROVIDERS_ANTHROPIC_API_KEY` | Anthropic API key | -| `PICOCLAW_PROVIDERS_GROQ_API_KEY` | Groq API key | -| `PICOCLAW_PROVIDERS_MISTRAL_API_KEY` | Mistral API key | -| `PICOCLAW_PROVIDERS_DEEPSEEK_API_KEY` | DeepSeek API key | - -#### Channel Config (optional) - -| Variable | Description | -|----------|-------------| -| `PICOCLAW_CHANNELS_TELEGRAM_ENABLED` | `true` / `false` | -| `PICOCLAW_CHANNELS_TELEGRAM_TOKEN` | Telegram bot token | -| `PICOCLAW_CHANNELS_TELEGRAM_ALLOW_FROM` | Comma-separated user IDs (e.g., `123,456`) | -| `PICOCLAW_CHANNELS_DISCORD_ENABLED` | `true` / `false` | -| `PICOCLAW_CHANNELS_DISCORD_TOKEN` | Discord bot token | -| `PICOCLAW_CHANNELS_DISCORD_ALLOW_FROM` | Comma-separated user IDs | - -#### Model Config (optional) - -| Variable | Default | Description | -|----------|---------|-------------| -| `PICOCLAW_AGENTS_DEFAULTS_PROVIDER` | `gemini` | LLM provider name | -| `PICOCLAW_AGENTS_DEFAULTS_MODEL` | `gemini-2.5-flash-lite` | Model name | -| `PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS` | `8192` | Max output tokens | -| `PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE` | `0.7` | Temperature | - -#### Other (optional) - -| Variable | Default | Description | -|----------|---------|-------------| -| `TZ` | `UTC` | Timezone (e.g., `Asia/Kolkata` for IST) | -| `PICOCLAW_HEARTBEAT_ENABLED` | `true` | Enable heartbeat | -| `PICOCLAW_HEARTBEAT_INTERVAL` | `30` | Heartbeat interval (minutes) | -| `PICOCLAW_TOOLS_WEB_DUCKDUCKGO_ENABLED` | `true` | DuckDuckGo search | -| `PICOCLAW_TOOLS_WEB_BRAVE_ENABLED` | `false` | Brave search | -| `PICOCLAW_TOOLS_WEB_BRAVE_API_KEY` | | Brave API key | - -#### โœ… Advantages -- **Zero JSON knowledge needed** โ€” just set key=value pairs -- **Easiest to set up** โ€” add a few env vars and deploy -- **Good for quick testing** โ€” get running in under a minute - -#### โŒ Limitations -- **No custom providers** โ€” only the built-in providers are supported (Gemini, OpenRouter, OpenAI, Anthropic, Groq, Mistral, DeepSeek, Zhipu, Moonshot, Nvidia, vLLM). You **cannot** add Ollama or other custom OpenAI-compatible providers -- **No custom API base URLs** โ€” you can't override `api_base` for providers (needed for self-hosted models) -- **Limited channel support** โ€” only Telegram, Discord, Slack, and LINE are configurable. Feishu, DingTalk, QQ, WhatsApp, MaixCam, and OneBot are **not** configurable via env vars -- **No proxy settings** โ€” provider proxy configuration is not available -- **Hardcoded defaults** โ€” many settings like `max_results`, `webhook_port`, etc. use hardcoded defaults that can't be changed -- **Allow-lists are comma-separated strings** โ€” works but less flexible than JSON arrays (no spaces in IDs) - -> **๐Ÿ’ก Tip:** Start with Method 3 to get running quickly, then switch to Method 1 when you need more control. - ---- - -## ๐Ÿ”ง Running Agent & Doctor Commands - -The gateway service runs automatically. To run one-shot commands, SSH into your Coolify server and use: - -```bash -# Run agent mode (one-shot question) -docker compose -f docker-compose-coolify.yml --profile agent run --rm picoclaw-agent -m "Hello!" - -# Run agent mode (interactive) -docker compose -f docker-compose-coolify.yml --profile agent run --rm picoclaw-agent - -# Run doctor (diagnostics) -docker compose -f docker-compose-coolify.yml --profile doctor run --rm picoclaw-doctor -``` - -### ๐ŸŽฎ Chat Commands (Telegram/Discord) - -You can check status and swap models directly from your chat app: - -| Command | Action | -|---------|--------| -| `/models` | View active model, provider, and all configured endpoints. | -| `/model ` | Switch the **model name** (keeping current provider). | -| `/model /` | Switch **both** provider and model (e.g., `vllm/qwen3-coder-next:cloud`). | - -> [!NOTE] -> Changes made via `/model` are active in memory. If the container restarts, it will revert to the default model defined in your `PICOCLAW_CONFIG_JSON` or environment variables. - ---- - -## ๏ฟฝ๏ธ Troubleshooting - -### "No API key configured" -Your config isn't being loaded. Check: -- For Method 1: Is `PICOCLAW_CONFIG_JSON` set? Is it valid JSON? -- For Method 3: Is `PICOCLAW_PROVIDERS_GEMINI_API_KEY` set? -- Check container logs: `docker logs picoclaw-gateway` โ€” look for the `๐Ÿ“ Using config from...` line. - -### Container keeps restarting -Check logs: `docker logs picoclaw-gateway --tail 50` - -Common issues: -- Invalid JSON in `PICOCLAW_CONFIG_JSON` (use a validator!) -- Missing API key for the configured provider - -### Build fails -- Ensure you're using the `deploy/coolify` branch -- Check that both `Dockerfile.coolify` and `entrypoint-coolify.sh` exist in the repo - ---- - -## ๐Ÿ“ File Structure (Coolify-specific) - -``` -picoclaw/ -โ”œโ”€โ”€ docker-compose-coolify.yml # Coolify-optimized compose file -โ”œโ”€โ”€ Dockerfile.coolify # Coolify Dockerfile with entrypoint -โ”œโ”€โ”€ entrypoint-coolify.sh # Config generator script -โ”œโ”€โ”€ Dockerfile # Original Dockerfile (not used by Coolify) -โ”œโ”€โ”€ docker-compose.yml # Original compose (not used by Coolify) -โ””โ”€โ”€ config/ - โ””โ”€โ”€ config.example.json # Reference config with all options -``` diff --git a/Dockerfile b/Dockerfile index dd98ec0bd..1a442d7da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,15 +22,16 @@ FROM alpine:3.23 RUN apk add --no-cache ca-certificates tzdata curl -# Health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD wget -q --spider http://localhost:18790/health || exit 1 - # Copy binary COPY --from=builder /src/build/picoclaw /usr/local/bin/picoclaw # Create picoclaw home directory RUN /usr/local/bin/picoclaw onboard +# Set environment variables for testing +ENV PICOCLAW_AGENTS_DEFAULTS_PROVIDER="gemini" +ENV PICOCLAW_AGENTS_DEFAULTS_MODEL="gemini-1.5-flash" +ENV PICOCLAW_PROVIDERS_GEMINI_API_KEY="" + ENTRYPOINT ["picoclaw"] CMD ["gateway"] diff --git a/Dockerfile.coolify b/Dockerfile.coolify deleted file mode 100644 index 8fe22ed43..000000000 --- a/Dockerfile.coolify +++ /dev/null @@ -1,41 +0,0 @@ -# ============================================================ -# PicoClaw Dockerfile โ€” Coolify Edition -# Uses an entrypoint script that generates config.json from -# environment variables at container startup. -# ============================================================ - -# Stage 1: Build the picoclaw binary -FROM golang:1.26.0-alpine AS builder - -RUN apk add --no-cache git make - -WORKDIR /src - -# Cache dependencies -COPY go.mod go.sum ./ -RUN go mod download - -# Copy source and build -COPY . . -RUN make build - -# Stage 2: Minimal runtime image -FROM alpine:3.23 - -RUN apk add --no-cache ca-certificates tzdata curl - -# Copy binary -COPY --from=builder /src/build/picoclaw /usr/local/bin/picoclaw - -# Create picoclaw home directory -RUN /usr/local/bin/picoclaw onboard - -# Copy the entrypoint script that generates config from env vars -COPY entrypoint-coolify.sh /usr/local/bin/entrypoint-coolify.sh -RUN chmod +x /usr/local/bin/entrypoint-coolify.sh - -# Default env vars (can be overridden by Coolify) -ENV TZ=UTC - -ENTRYPOINT ["/usr/local/bin/entrypoint-coolify.sh"] -CMD ["gateway"] diff --git a/Makefile b/Makefile index 9786b30bb..058fdb790 100644 --- a/Makefile +++ b/Makefile @@ -119,7 +119,7 @@ clean: @rm -rf $(BUILD_DIR) @echo "Clean complete" -## vet: Run go vet for static analysis +## fmt: Format Go code vet: @$(GO) vet ./... @@ -131,19 +131,11 @@ test: fmt: @$(GO) fmt ./... -## deps: Download dependencies +## deps: Update dependencies deps: - @$(GO) mod download - @$(GO) mod verify - -## update-deps: Update dependencies -update-deps: @$(GO) get -u ./... @$(GO) mod tidy -## check: Run vet, fmt, and verify dependencies -check: deps fmt vet test - ## run: Build and run picoclaw run: build @$(BUILD_DIR)/$(BINARY_NAME) $(ARGS) diff --git a/README.md b/README.md index 06b09f0dd..a61a1abf4 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ > * **NO CRYPTO:** PicoClaw has **NO** official token/coin. All claims on `pump.fun` or other trading platforms are **SCAMS**. > * **OFFICIAL DOMAIN:** The **ONLY** official website is **[picoclaw.io](https://picoclaw.io)**, and company website is **[sipeed.com](https://sipeed.com)** > * **Warning:** Many `.ai/.org/.com/.net/...` domains are registered by third parties. -> * **Warning:** picoclaw is in early development now and may have unresolved network security issues. Do not deploy to production environments before the v1.0 release. +> ## ๐Ÿ“ข News @@ -172,32 +172,6 @@ docker compose --profile gateway build --no-cache docker compose --profile gateway up -d ``` -### โ˜๏ธ Deploy on Coolify (3 Steps) - -Deploy PicoClaw on [Coolify](https://coolify.io) in under 5 minutes: - -**1. Create service** โ€” In Coolify: **+ New** โ†’ **Docker Compose** โ†’ connect your fork of this repo - - Branch: `main` - - Compose file: `docker-compose-coolify.yml` - -**2. Add env vars** โ€” Go to **Environment Variables** tab and add: - -| Variable | Value | -|----------|-------| -| `PICOCLAW_PROVIDERS_GEMINI_API_KEY` | Your [Gemini API key](https://aistudio.google.com/apikey) | -| `PICOCLAW_CHANNELS_TELEGRAM_ENABLED` | `true` *(if using Telegram)* | -| `PICOCLAW_CHANNELS_TELEGRAM_TOKEN` | Your bot token from [@BotFather](https://t.me/BotFather) | -| `TZ` | `Asia/Kolkata` *(or your timezone)* | - -**3. Deploy!** ๐Ÿš€ - -> [!TIP] -> **Need more control?** (custom providers like Ollama, allowlists, multiple channels) -> Set `PICOCLAW_CONFIG_JSON` with your full config as a single env var. -> See the complete guide: **[COOLIFY.md](COOLIFY.md)** - ---- - ### ๐Ÿš€ Quick Start > [!TIP] diff --git a/README.zh.md b/README.zh.md index 5a1c3c50b..f94abce88 100644 --- a/README.zh.md +++ b/README.zh.md @@ -45,8 +45,8 @@ > * **ๆ— ๅŠ ๅฏ†่ดงๅธ (NO CRYPTO):** PicoClaw **ๆฒกๆœ‰** ๅ‘่กŒไปปไฝ•ๅฎ˜ๆ–นไปฃๅธใ€Token ๆˆ–่™šๆ‹Ÿ่ดงๅธใ€‚ๆ‰€ๆœ‰ๅœจ `pump.fun` ๆˆ–ๅ…ถไป–ไบคๆ˜“ๅนณๅฐไธŠ็š„็›ธๅ…ณๅฃฐ็งฐๅ‡ไธบ **่ฏˆ้ช—**ใ€‚ > * **ๅฎ˜ๆ–นๅŸŸๅ:** ๅ”ฏไธ€็š„ๅฎ˜ๆ–น็ฝ‘็ซ™ๆ˜ฏ **[picoclaw.io](https://picoclaw.io)**๏ผŒๅ…ฌๅธๅฎ˜็ฝ‘ๆ˜ฏ **[sipeed.com](https://sipeed.com)**ใ€‚ > * **่ญฆๆƒ•:** ่ฎธๅคš `.ai/.org/.com/.net/...` ๅŽ็ผ€็š„ๅŸŸๅ่ขซ็ฌฌไธ‰ๆ–นๆŠขๆณจ๏ผŒ่ฏทๅ‹ฟ่ฝปไฟกใ€‚ -> * **ๆณจๆ„:** picoclawๆญฃๅœจๅˆๆœŸ็š„ๅฟซ้€ŸๅŠŸ่ƒฝๅผ€ๅ‘้˜ถๆฎต๏ผŒๅฏ่ƒฝๆœ‰ๅฐšๆœชไฟฎๅค็š„็ฝ‘็ปœๅฎ‰ๅ…จ้—ฎ้ข˜๏ผŒๅœจ1.0ๆญฃๅผ็‰ˆๅ‘ๅธƒๅ‰๏ผŒ่ฏทไธ่ฆๅฐ†ๅ…ถ้ƒจ็ฝฒๅˆฐ็”Ÿไบง็Žฏๅขƒไธญ - +> +> ## ๐Ÿ“ข ๆ–ฐ้—ป (News) diff --git a/docker-compose-coolify.yml b/docker-compose-coolify.yml deleted file mode 100644 index 6f03e08b3..000000000 --- a/docker-compose-coolify.yml +++ /dev/null @@ -1,167 +0,0 @@ -# ============================================================ -# PicoClaw โ€” Coolify-Optimized Docker Compose -# ============================================================ -# -# DEPLOYMENT STEPS (Coolify): -# 1. Create a new service โ†’ Docker Compose -# 2. Point source to this repository -# 3. Set "Docker Compose file" path to: picoclaw/docker-compose-coolify.yml -# 4. Add environment variables in Coolify's UI (see below) -# 5. Deploy! -# -# RECOMMENDED: Use PICOCLAW_CONFIG_JSON for full control: -# PICOCLAW_CONFIG_JSON โ€” Full JSON config (overrides everything below) -# -# ALTERNATIVE: Individual environment variables: -# GEMINI_API_KEY โ€” Your Gemini API key -# -# OPTIONAL ENVIRONMENT VARIABLES: -# LLM_PROVIDER โ€” LLM provider (e.g. gemini, vllm, openrouter) -# LLM_MODEL โ€” Model name (e.g. gemini-2.5-flash-lite) -# TELEGRAM_BOT_TOKEN โ€” Telegram bot token -# DISCORD_BOT_TOKEN โ€” Discord bot token -# OPENROUTER_API_KEY โ€” OpenRouter API key -# OPENAI_API_KEY โ€” OpenAI API key -# ANTHROPIC_API_KEY โ€” Anthropic API key -# GROQ_API_KEY โ€” Groq API key (also enables voice transcription) -# BRAVE_SEARCH_API_KEY โ€” Brave Search API key -# LINE_CHANNEL_SECRET โ€” LINE channel secret -# LINE_CHANNEL_ACCESS_TOKEN โ€” LINE channel access token -# TZ โ€” Timezone (default: UTC) -# -# ============================================================ - -services: - # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - # PicoClaw Gateway (Long-running Bot) - # This is the main service that Coolify will auto-start - # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - picoclaw-gateway: - build: - context: . - dockerfile: Dockerfile.coolify - container_name: picoclaw-gateway - restart: unless-stopped - - # โ”€โ”€ Environment Variables โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - # If PICOCLAW_CONFIG_JSON is set, it takes full priority. - # Individual vars below are only used as fallback (Method 3). - environment: - # -- Full JSON config (Method 1 โ€” overrides everything below) -- - - PICOCLAW_CONFIG_JSON=${PICOCLAW_CONFIG_JSON:-} - - # -- Core LLM Config (only used if PICOCLAW_CONFIG_JSON is empty) -- - - LLM_PROVIDER=${LLM_PROVIDER:-} - - LLM_MODEL=${LLM_MODEL:-} - - # -- Provider API Keys -- - - GEMINI_API_KEY=${GEMINI_API_KEY:-} - - OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-} - - OPENAI_API_KEY=${OPENAI_API_KEY:-} - - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} - - GROQ_API_KEY=${GROQ_API_KEY:-} - - # -- Chat Channel Tokens -- - - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-} - - DISCORD_BOT_TOKEN=${DISCORD_BOT_TOKEN:-} - - LINE_CHANNEL_SECRET=${LINE_CHANNEL_SECRET:-} - - LINE_CHANNEL_ACCESS_TOKEN=${LINE_CHANNEL_ACCESS_TOKEN:-} - - # -- Web Search -- - - BRAVE_SEARCH_API_KEY=${BRAVE_SEARCH_API_KEY:-} - - BRAVE_SEARCH_ENABLED=${BRAVE_SEARCH_ENABLED:-} - - DUCKDUCKGO_ENABLED=${DUCKDUCKGO_ENABLED:-} - - # -- Heartbeat -- - - HEARTBEAT_ENABLED=${HEARTBEAT_ENABLED:-} - - HEARTBEAT_INTERVAL=${HEARTBEAT_INTERVAL:-} - - # -- Timezone -- - - TZ=${TZ:-UTC} - - # โ”€โ”€ Volumes โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - volumes: - # Persistent workspace โ€” sessions, memory, logs survive redeploys - # Note: config.json is created by 'picoclaw onboard' during Docker build - # and all settings are overridden via environment variables above - - picoclaw-workspace:/root/.picoclaw/workspace - - # โ”€โ”€ Ports โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - # Expose ports if using LINE webhook or MaixCAM channel - # Coolify will auto-detect and configure these - ports: - - "18790:18790" # MaixCAM / Gateway - - "18791:18791" # LINE Webhook - - # โ”€โ”€ Health Check โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - # Coolify uses this to determine if the container is healthy - healthcheck: - test: ["CMD", "picoclaw", "status"] - interval: 60s - timeout: 10s - retries: 3 - start_period: 30s - - # โ”€โ”€ Command โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - command: ["gateway"] - - # โ”€โ”€ Logging โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "3" - - # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - # PicoClaw Agent (One-shot / Interactive) - # Not auto-started by Coolify (has profile). - # Run manually via SSH: - # docker compose -f docker-compose-coolify.yml run --rm picoclaw-agent -m "Hello" - # docker compose -f docker-compose-coolify.yml run --rm picoclaw-agent - # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - picoclaw-agent: - build: - context: . - dockerfile: Dockerfile - container_name: picoclaw-agent - profiles: - - agent - environment: - - PICOCLAW_AGENTS_DEFAULTS_PROVIDER=${LLM_PROVIDER:-} - - PICOCLAW_AGENTS_DEFAULTS_MODEL=${LLM_MODEL:-} - - PICOCLAW_PROVIDERS_GEMINI_API_KEY=${GEMINI_API_KEY:-} - - PICOCLAW_PROVIDERS_OPENROUTER_API_KEY=${OPENROUTER_API_KEY:-} - - PICOCLAW_PROVIDERS_OPENAI_API_KEY=${OPENAI_API_KEY:-} - - PICOCLAW_PROVIDERS_ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} - - TZ=${TZ:-UTC} - volumes: - - picoclaw-workspace:/root/.picoclaw/workspace - entrypoint: ["picoclaw", "agent"] - stdin_open: true - tty: true - - # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - # PicoClaw Doctor (Diagnostic Tool) - # Not auto-started by Coolify (has profile). - # Run manually via SSH: - # docker compose -f docker-compose-coolify.yml run --rm picoclaw-doctor - # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - picoclaw-doctor: - build: - context: . - dockerfile: Dockerfile - container_name: picoclaw-doctor - profiles: - - doctor - environment: - - PICOCLAW_AGENTS_DEFAULTS_PROVIDER=${LLM_PROVIDER:-} - - PICOCLAW_AGENTS_DEFAULTS_MODEL=${LLM_MODEL:-} - - PICOCLAW_PROVIDERS_GEMINI_API_KEY=${GEMINI_API_KEY:-} - - TZ=${TZ:-UTC} - volumes: - - picoclaw-workspace:/root/.picoclaw/workspace - entrypoint: ["picoclaw", "doctor"] - -volumes: - picoclaw-workspace: - driver: local diff --git a/entrypoint-coolify.sh b/entrypoint-coolify.sh deleted file mode 100644 index 373dcfbd4..000000000 --- a/entrypoint-coolify.sh +++ /dev/null @@ -1,170 +0,0 @@ -#!/bin/sh -# ============================================================ -# PicoClaw Coolify Entrypoint -# Generates config.json before starting PicoClaw -# -# CONFIG PRIORITY (first match wins): -# 1. PICOCLAW_CONFIG_JSON env var โ€” paste your entire JSON config -# 2. Mounted file at /config/config.json โ€” use Coolify Storages -# 3. Auto-generated from individual env vars (basic setup) -# ============================================================ -set -e - -CONFIG_DIR="/root/.picoclaw" -CONFIG_FILE="${CONFIG_DIR}/config.json" - -mkdir -p "${CONFIG_DIR}" - -# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -# METHOD 1: Full JSON config via env var -# Set PICOCLAW_CONFIG_JSON in Coolify env vars -# with your entire config.json content -# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -if [ -n "${PICOCLAW_CONFIG_JSON}" ]; then - echo "๐Ÿ“ Using config from PICOCLAW_CONFIG_JSON env var" - echo "${PICOCLAW_CONFIG_JSON}" > "${CONFIG_FILE}" - - # CRITICAL: Unset all PICOCLAW_* env vars (except PICOCLAW_CONFIG_JSON) - # so that Go's env.Parse() does NOT override values from the JSON file. - # Without this, Dockerfile ENV defaults (e.g. PICOCLAW_AGENTS_DEFAULTS_PROVIDER=gemini) - # would silently overwrite the user's JSON config every time. - for var in $(env | grep '^PICOCLAW_' | cut -d= -f1); do - if [ "$var" != "PICOCLAW_CONFIG_JSON" ]; then - unset "$var" - fi - done - - exec picoclaw "$@" -fi - -# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -# METHOD 2: Mounted config file -# In Coolify โ†’ Storages โ†’ Add: -# Source: /data/coolify/applications//config.json -# Destination: /config/config.json -# Then paste your JSON in the file content -# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -if [ -f "/config/config.json" ]; then - echo "๐Ÿ“ Using mounted config from /config/config.json" - cp /config/config.json "${CONFIG_FILE}" - - # Same protection as Method 1: unset PICOCLAW_* env vars - for var in $(env | grep '^PICOCLAW_' | cut -d= -f1); do - unset "$var" - done - - exec picoclaw "$@" -fi - -# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -# METHOD 3: Auto-generate from individual env vars -# Good for simple setups (Gemini + Telegram, etc.) -# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -echo "๐Ÿ“ Generating config from individual env vars" - -# Helper: comma-separated string โ†’ JSON array -csv_to_json_array() { - input="$1" - if [ -z "$input" ]; then echo "[]"; return; fi - result="[" - first=true - OLD_IFS="$IFS"; IFS="," - for item in $input; do - item=$(echo "$item" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') - if [ -n "$item" ]; then - if [ "$first" = true ]; then first=false; else result="${result},"; fi - result="${result}\"${item}\"" - fi - done - IFS="$OLD_IFS" - echo "${result}]" -} - -# โ”€โ”€ Resolve env vars โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -# Accept both Coolify-friendly short names and PICOCLAW_* internal names -R_PROVIDER="${PICOCLAW_AGENTS_DEFAULTS_PROVIDER:-${LLM_PROVIDER:-gemini}}" -R_MODEL="${PICOCLAW_AGENTS_DEFAULTS_MODEL:-${LLM_MODEL:-gemini-2.5-flash-lite}}" - -R_GEMINI_KEY="${PICOCLAW_PROVIDERS_GEMINI_API_KEY:-${GEMINI_API_KEY:-}}" -R_OPENROUTER_KEY="${PICOCLAW_PROVIDERS_OPENROUTER_API_KEY:-${OPENROUTER_API_KEY:-}}" -R_OPENAI_KEY="${PICOCLAW_PROVIDERS_OPENAI_API_KEY:-${OPENAI_API_KEY:-}}" -R_ANTHROPIC_KEY="${PICOCLAW_PROVIDERS_ANTHROPIC_API_KEY:-${ANTHROPIC_API_KEY:-}}" -R_GROQ_KEY="${PICOCLAW_PROVIDERS_GROQ_API_KEY:-${GROQ_API_KEY:-}}" -R_MISTRAL_KEY="${PICOCLAW_PROVIDERS_MISTRAL_API_KEY:-${MISTRAL_API_KEY:-}}" -R_DEEPSEEK_KEY="${PICOCLAW_PROVIDERS_DEEPSEEK_API_KEY:-${DEEPSEEK_API_KEY:-}}" -R_VLLM_KEY="${PICOCLAW_PROVIDERS_VLLM_API_KEY:-${VLLM_API_KEY:-}}" -R_VLLM_BASE="${PICOCLAW_PROVIDERS_VLLM_API_BASE:-${VLLM_API_BASE:-}}" - -R_TELEGRAM="${PICOCLAW_CHANNELS_TELEGRAM_TOKEN:-${TELEGRAM_BOT_TOKEN:-}}" -R_DISCORD="${PICOCLAW_CHANNELS_DISCORD_TOKEN:-${DISCORD_BOT_TOKEN:-}}" -R_LINE_SECRET="${PICOCLAW_CHANNELS_LINE_CHANNEL_SECRET:-${LINE_CHANNEL_SECRET:-}}" -R_LINE_ACCESS="${PICOCLAW_CHANNELS_LINE_CHANNEL_ACCESS_TOKEN:-${LINE_CHANNEL_ACCESS_TOKEN:-}}" - -R_BRAVE_KEY="${PICOCLAW_TOOLS_WEB_BRAVE_API_KEY:-${BRAVE_SEARCH_API_KEY:-}}" -R_BRAVE_ON="${PICOCLAW_TOOLS_WEB_BRAVE_ENABLED:-${BRAVE_SEARCH_ENABLED:-false}}" -R_DDG_ON="${PICOCLAW_TOOLS_WEB_DUCKDUCKGO_ENABLED:-${DUCKDUCKGO_ENABLED:-true}}" -R_HB_ON="${PICOCLAW_HEARTBEAT_ENABLED:-${HEARTBEAT_ENABLED:-true}}" -R_HB_INT="${PICOCLAW_HEARTBEAT_INTERVAL:-${HEARTBEAT_INTERVAL:-30}}" - -# โ”€โ”€ Auto-enable channels when token is provided โ”€โ”€ -if [ -n "$R_TELEGRAM" ]; then TG_ON="true"; else TG_ON="${PICOCLAW_CHANNELS_TELEGRAM_ENABLED:-false}"; fi -if [ -n "$R_DISCORD" ]; then DC_ON="true"; else DC_ON="${PICOCLAW_CHANNELS_DISCORD_ENABLED:-false}"; fi -if [ -n "$R_LINE_SECRET" ] && [ -n "$R_LINE_ACCESS" ]; then LN_ON="true"; else LN_ON="${PICOCLAW_CHANNELS_LINE_ENABLED:-false}"; fi - -TELEGRAM_ALLOW=$(csv_to_json_array "${PICOCLAW_CHANNELS_TELEGRAM_ALLOW_FROM:-}") -DISCORD_ALLOW=$(csv_to_json_array "${PICOCLAW_CHANNELS_DISCORD_ALLOW_FROM:-}") -SLACK_ALLOW=$(csv_to_json_array "${PICOCLAW_CHANNELS_SLACK_ALLOW_FROM:-}") -LINE_ALLOW=$(csv_to_json_array "${PICOCLAW_CHANNELS_LINE_ALLOW_FROM:-}") - -cat > "${CONFIG_FILE}" <