mirror of
https://github.com/clawdbot/clawdbot.git
synced 2026-01-31 19:37:45 +01:00
Merge branch 'main' into feat/plan-mode
This commit is contained in:
@@ -7,6 +7,10 @@
|
|||||||
[exclude-files]
|
[exclude-files]
|
||||||
# pnpm lockfiles contain lots of high-entropy package integrity blobs.
|
# pnpm lockfiles contain lots of high-entropy package integrity blobs.
|
||||||
pattern = (^|/)pnpm-lock\.yaml$
|
pattern = (^|/)pnpm-lock\.yaml$
|
||||||
|
# Generated output and vendored assets.
|
||||||
|
pattern = (^|/)(dist|vendor)/
|
||||||
|
# Local config file with allowlist patterns.
|
||||||
|
pattern = (^|/)\.detect-secrets\.cfg$
|
||||||
|
|
||||||
[exclude-lines]
|
[exclude-lines]
|
||||||
# Fastlane checks for private key marker; not a real key.
|
# Fastlane checks for private key marker; not a real key.
|
||||||
|
|||||||
17
.github/actionlint.yaml
vendored
Normal file
17
.github/actionlint.yaml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# actionlint configuration
|
||||||
|
# https://github.com/rhysd/actionlint/blob/main/docs/config.md
|
||||||
|
|
||||||
|
self-hosted-runner:
|
||||||
|
labels:
|
||||||
|
# Blacksmith CI runners
|
||||||
|
- blacksmith-4vcpu-ubuntu-2404
|
||||||
|
- blacksmith-4vcpu-windows-2025
|
||||||
|
|
||||||
|
# Ignore patterns for known issues
|
||||||
|
paths:
|
||||||
|
.github/workflows/**/*.yml:
|
||||||
|
ignore:
|
||||||
|
# Ignore shellcheck warnings (we run shellcheck separately)
|
||||||
|
- 'shellcheck reported issue.+'
|
||||||
|
# Ignore intentional if: false for disabled jobs
|
||||||
|
- 'constant expression "false" in condition'
|
||||||
113
.github/dependabot.yml
vendored
Normal file
113
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
# Dependabot configuration
|
||||||
|
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
registries:
|
||||||
|
npm-npmjs:
|
||||||
|
type: npm-registry
|
||||||
|
url: https://registry.npmjs.org
|
||||||
|
replaces-base: true
|
||||||
|
|
||||||
|
updates:
|
||||||
|
# npm dependencies (root)
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
groups:
|
||||||
|
production:
|
||||||
|
dependency-type: production
|
||||||
|
update-types:
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
|
development:
|
||||||
|
dependency-type: development
|
||||||
|
update-types:
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
registries:
|
||||||
|
- npm-npmjs
|
||||||
|
|
||||||
|
# GitHub Actions
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
groups:
|
||||||
|
actions:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
update-types:
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
|
open-pull-requests-limit: 5
|
||||||
|
|
||||||
|
# Swift Package Manager - macOS app
|
||||||
|
- package-ecosystem: swift
|
||||||
|
directory: /apps/macos
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
groups:
|
||||||
|
swift-deps:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
update-types:
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
|
open-pull-requests-limit: 5
|
||||||
|
|
||||||
|
# Swift Package Manager - shared ClawdbotKit
|
||||||
|
- package-ecosystem: swift
|
||||||
|
directory: /apps/shared/ClawdbotKit
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
groups:
|
||||||
|
swift-deps:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
update-types:
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
|
open-pull-requests-limit: 5
|
||||||
|
|
||||||
|
# Swift Package Manager - Swabble
|
||||||
|
- package-ecosystem: swift
|
||||||
|
directory: /Swabble
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
groups:
|
||||||
|
swift-deps:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
update-types:
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
|
open-pull-requests-limit: 5
|
||||||
|
|
||||||
|
# Gradle - Android app
|
||||||
|
- package-ecosystem: gradle
|
||||||
|
directory: /apps/android
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
cooldown:
|
||||||
|
default-days: 7
|
||||||
|
groups:
|
||||||
|
android-deps:
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
update-types:
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
|
open-pull-requests-limit: 5
|
||||||
186
.github/labeler.yml
vendored
Normal file
186
.github/labeler.yml
vendored
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
"channel: bluebubbles":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/bluebubbles/**"
|
||||||
|
- "docs/channels/bluebubbles.md"
|
||||||
|
"channel: discord":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/discord/**"
|
||||||
|
- "extensions/discord/**"
|
||||||
|
- "docs/channels/discord.md"
|
||||||
|
"channel: googlechat":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/googlechat/**"
|
||||||
|
- "docs/channels/googlechat.md"
|
||||||
|
"channel: imessage":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/imessage/**"
|
||||||
|
- "extensions/imessage/**"
|
||||||
|
- "docs/channels/imessage.md"
|
||||||
|
"channel: line":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/line/**"
|
||||||
|
- "docs/channels/line.md"
|
||||||
|
"channel: matrix":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/matrix/**"
|
||||||
|
- "docs/channels/matrix.md"
|
||||||
|
"channel: mattermost":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/mattermost/**"
|
||||||
|
- "docs/channels/mattermost.md"
|
||||||
|
"channel: msteams":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/msteams/**"
|
||||||
|
- "docs/channels/msteams.md"
|
||||||
|
"channel: nextcloud-talk":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/nextcloud-talk/**"
|
||||||
|
- "docs/channels/nextcloud-talk.md"
|
||||||
|
"channel: nostr":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/nostr/**"
|
||||||
|
- "docs/channels/nostr.md"
|
||||||
|
"channel: signal":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/signal/**"
|
||||||
|
- "extensions/signal/**"
|
||||||
|
- "docs/channels/signal.md"
|
||||||
|
"channel: slack":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/slack/**"
|
||||||
|
- "extensions/slack/**"
|
||||||
|
- "docs/channels/slack.md"
|
||||||
|
"channel: telegram":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/telegram/**"
|
||||||
|
- "extensions/telegram/**"
|
||||||
|
- "docs/channels/telegram.md"
|
||||||
|
"channel: tlon":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/tlon/**"
|
||||||
|
- "docs/channels/tlon.md"
|
||||||
|
"channel: voice-call":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/voice-call/**"
|
||||||
|
"channel: whatsapp-web":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/web/**"
|
||||||
|
- "extensions/whatsapp/**"
|
||||||
|
- "docs/channels/whatsapp.md"
|
||||||
|
"channel: zalo":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/zalo/**"
|
||||||
|
- "docs/channels/zalo.md"
|
||||||
|
"channel: zalouser":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/zalouser/**"
|
||||||
|
- "docs/channels/zalouser.md"
|
||||||
|
|
||||||
|
"app: android":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "apps/android/**"
|
||||||
|
- "docs/platforms/android.md"
|
||||||
|
"app: ios":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "apps/ios/**"
|
||||||
|
- "docs/platforms/ios.md"
|
||||||
|
"app: macos":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "apps/macos/**"
|
||||||
|
- "docs/platforms/macos.md"
|
||||||
|
- "docs/platforms/mac/**"
|
||||||
|
"app: web-ui":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "ui/**"
|
||||||
|
- "src/gateway/control-ui.ts"
|
||||||
|
- "src/gateway/control-ui-shared.ts"
|
||||||
|
- "src/gateway/protocol/**"
|
||||||
|
- "src/gateway/server-methods/chat.ts"
|
||||||
|
- "src/infra/control-ui-assets.ts"
|
||||||
|
|
||||||
|
"gateway":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/gateway/**"
|
||||||
|
- "src/daemon/**"
|
||||||
|
- "docs/gateway/**"
|
||||||
|
|
||||||
|
"docs":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "docs/**"
|
||||||
|
- "docs.acp.md"
|
||||||
|
|
||||||
|
"cli":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "src/cli/**"
|
||||||
|
|
||||||
|
"security":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "docs/cli/security.md"
|
||||||
|
- "docs/gateway/security.md"
|
||||||
|
|
||||||
|
"extensions: copilot-proxy":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/copilot-proxy/**"
|
||||||
|
"extensions: diagnostics-otel":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/diagnostics-otel/**"
|
||||||
|
"extensions: google-antigravity-auth":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/google-antigravity-auth/**"
|
||||||
|
"extensions: google-gemini-cli-auth":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/google-gemini-cli-auth/**"
|
||||||
|
"extensions: llm-task":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/llm-task/**"
|
||||||
|
"extensions: lobster":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/lobster/**"
|
||||||
|
"extensions: memory-core":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/memory-core/**"
|
||||||
|
"extensions: memory-lancedb":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/memory-lancedb/**"
|
||||||
|
"extensions: open-prose":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/open-prose/**"
|
||||||
|
"extensions: qwen-portal-auth":
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- "extensions/qwen-portal-auth/**"
|
||||||
59
.github/workflows/auto-response.yml
vendored
Normal file
59
.github/workflows/auto-response.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
name: Auto response
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [labeled]
|
||||||
|
pull_request:
|
||||||
|
types: [labeled]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
auto-response:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Handle labeled items
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const rules = [
|
||||||
|
{
|
||||||
|
label: "skill-clawdhub",
|
||||||
|
close: true,
|
||||||
|
message:
|
||||||
|
"Thanks for the contribution! New skills should be published to Clawdhub for everyone to use. We’re keeping the core lean on skills, so I’m closing this out.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const labelName = context.payload.label?.name;
|
||||||
|
if (!labelName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rule = rules.find((item) => item.label === labelName);
|
||||||
|
if (!rule) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const issueNumber = context.payload.issue?.number ?? context.payload.pull_request?.number;
|
||||||
|
if (!issueNumber) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
body: rule.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (rule.close) {
|
||||||
|
await github.rest.issues.update({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
state: "closed",
|
||||||
|
});
|
||||||
|
}
|
||||||
90
.github/workflows/ci.yml
vendored
90
.github/workflows/ci.yml
vendored
@@ -32,20 +32,29 @@ jobs:
|
|||||||
node-version: 22.x
|
node-version: 22.x
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Setup pnpm (corepack retry)
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
corepack enable
|
||||||
|
for attempt in 1 2 3; do
|
||||||
|
if corepack prepare pnpm@10.23.0 --activate; then
|
||||||
|
pnpm -v
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "corepack prepare failed (attempt $attempt/3). Retrying..."
|
||||||
|
sleep $((attempt * 10))
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
|
||||||
- name: Runtime versions
|
- name: Runtime versions
|
||||||
run: |
|
run: |
|
||||||
node -v
|
node -v
|
||||||
npm -v
|
npm -v
|
||||||
|
pnpm -v
|
||||||
|
|
||||||
- name: Capture node path
|
- name: Capture node path
|
||||||
run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV"
|
run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Enable corepack and pin pnpm
|
|
||||||
run: |
|
|
||||||
corepack enable
|
|
||||||
corepack prepare pnpm@10.23.0 --activate
|
|
||||||
pnpm -v
|
|
||||||
|
|
||||||
- name: Install dependencies (frozen)
|
- name: Install dependencies (frozen)
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
@@ -108,6 +117,20 @@ jobs:
|
|||||||
node-version: 22.x
|
node-version: 22.x
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Setup pnpm (corepack retry)
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
corepack enable
|
||||||
|
for attempt in 1 2 3; do
|
||||||
|
if corepack prepare pnpm@10.23.0 --activate; then
|
||||||
|
pnpm -v
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "corepack prepare failed (attempt $attempt/3). Retrying..."
|
||||||
|
sleep $((attempt * 10))
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
@@ -118,16 +141,11 @@ jobs:
|
|||||||
node -v
|
node -v
|
||||||
npm -v
|
npm -v
|
||||||
bun -v
|
bun -v
|
||||||
|
pnpm -v
|
||||||
|
|
||||||
- name: Capture node path
|
- name: Capture node path
|
||||||
run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV"
|
run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Enable corepack and pin pnpm
|
|
||||||
run: |
|
|
||||||
corepack enable
|
|
||||||
corepack prepare pnpm@10.23.0 --activate
|
|
||||||
pnpm -v
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
@@ -168,6 +186,8 @@ jobs:
|
|||||||
|
|
||||||
checks-windows:
|
checks-windows:
|
||||||
runs-on: blacksmith-4vcpu-windows-2025
|
runs-on: blacksmith-4vcpu-windows-2025
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -212,6 +232,20 @@ jobs:
|
|||||||
node-version: 22.x
|
node-version: 22.x
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Setup pnpm (corepack retry)
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
corepack enable
|
||||||
|
for attempt in 1 2 3; do
|
||||||
|
if corepack prepare pnpm@10.23.0 --activate; then
|
||||||
|
pnpm -v
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "corepack prepare failed (attempt $attempt/3). Retrying..."
|
||||||
|
sleep $((attempt * 10))
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
@@ -222,16 +256,11 @@ jobs:
|
|||||||
node -v
|
node -v
|
||||||
npm -v
|
npm -v
|
||||||
bun -v
|
bun -v
|
||||||
|
pnpm -v
|
||||||
|
|
||||||
- name: Capture node path
|
- name: Capture node path
|
||||||
run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV"
|
run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Enable corepack and pin pnpm
|
|
||||||
run: |
|
|
||||||
corepack enable
|
|
||||||
corepack prepare pnpm@10.23.0 --activate
|
|
||||||
pnpm -v
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
@@ -279,20 +308,29 @@ jobs:
|
|||||||
node-version: 22.x
|
node-version: 22.x
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Setup pnpm (corepack retry)
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
corepack enable
|
||||||
|
for attempt in 1 2 3; do
|
||||||
|
if corepack prepare pnpm@10.23.0 --activate; then
|
||||||
|
pnpm -v
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "corepack prepare failed (attempt $attempt/3). Retrying..."
|
||||||
|
sleep $((attempt * 10))
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
|
||||||
- name: Runtime versions
|
- name: Runtime versions
|
||||||
run: |
|
run: |
|
||||||
node -v
|
node -v
|
||||||
npm -v
|
npm -v
|
||||||
|
pnpm -v
|
||||||
|
|
||||||
- name: Capture node path
|
- name: Capture node path
|
||||||
run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV"
|
run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Enable corepack and pin pnpm
|
|
||||||
run: |
|
|
||||||
corepack enable
|
|
||||||
corepack prepare pnpm@10.23.0 --activate
|
|
||||||
pnpm -v
|
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
@@ -304,6 +342,8 @@ jobs:
|
|||||||
pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true || pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
|
pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true || pnpm install --frozen-lockfile --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
|
||||||
|
|
||||||
- name: Run ${{ matrix.task }}
|
- name: Run ${{ matrix.task }}
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
run: ${{ matrix.command }}
|
run: ${{ matrix.command }}
|
||||||
|
|
||||||
macos-app:
|
macos-app:
|
||||||
@@ -590,6 +630,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@v4
|
uses: gradle/actions/setup-gradle@v4
|
||||||
|
with:
|
||||||
|
gradle-version: 8.11.1
|
||||||
|
|
||||||
- name: Install Android SDK packages
|
- name: Install Android SDK packages
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
19
.github/workflows/install-smoke.yml
vendored
19
.github/workflows/install-smoke.yml
vendored
@@ -13,12 +13,19 @@ jobs:
|
|||||||
- name: Checkout CLI
|
- name: Checkout CLI
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm (corepack retry)
|
||||||
uses: pnpm/action-setup@v3
|
run: |
|
||||||
with:
|
set -euo pipefail
|
||||||
version: 10
|
corepack enable
|
||||||
- name: Enable Corepack
|
for attempt in 1 2 3; do
|
||||||
run: corepack enable
|
if corepack prepare pnpm@10.23.0 --activate; then
|
||||||
|
pnpm -v
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "corepack prepare failed (attempt $attempt/3). Retrying..."
|
||||||
|
sleep $((attempt * 10))
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
|
||||||
- name: Install pnpm deps (minimal)
|
- name: Install pnpm deps (minimal)
|
||||||
run: pnpm install --ignore-scripts --frozen-lockfile
|
run: pnpm install --ignore-scripts --frozen-lockfile
|
||||||
|
|||||||
23
.github/workflows/labeler.yml
vendored
Normal file
23
.github/workflows/labeler.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
name: Labeler
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, synchronize, reopened]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
label:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/create-github-app-token@v1
|
||||||
|
id: app-token
|
||||||
|
with:
|
||||||
|
app-id: "2729701"
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
- uses: actions/labeler@v5
|
||||||
|
with:
|
||||||
|
configuration-path: .github/labeler.yml
|
||||||
|
repo-token: ${{ steps.app-token.outputs.token }}
|
||||||
105
.pre-commit-config.yaml
Normal file
105
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Pre-commit hooks for clawdbot
|
||||||
|
# Install: prek install
|
||||||
|
# Run manually: prek run --all-files
|
||||||
|
#
|
||||||
|
# See https://pre-commit.com for more information
|
||||||
|
|
||||||
|
repos:
|
||||||
|
# Basic file hygiene
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v6.0.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
exclude: '^(docs/|dist/|vendor/|.*\.snap$)'
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
exclude: '^(docs/|dist/|vendor/|.*\.snap$)'
|
||||||
|
- id: check-yaml
|
||||||
|
args: [--allow-multiple-documents]
|
||||||
|
- id: check-added-large-files
|
||||||
|
args: [--maxkb=500]
|
||||||
|
- id: check-merge-conflict
|
||||||
|
|
||||||
|
# Secret detection (same as CI)
|
||||||
|
- repo: https://github.com/Yelp/detect-secrets
|
||||||
|
rev: v1.5.0
|
||||||
|
hooks:
|
||||||
|
- id: detect-secrets
|
||||||
|
args:
|
||||||
|
- --baseline
|
||||||
|
- .secrets.baseline
|
||||||
|
- --exclude-files
|
||||||
|
- '(^|/)(dist/|vendor/|pnpm-lock\.yaml$|\.detect-secrets\.cfg$)'
|
||||||
|
- --exclude-lines
|
||||||
|
- 'key_content\.include\?\("BEGIN PRIVATE KEY"\)'
|
||||||
|
- --exclude-lines
|
||||||
|
- 'case \.apiKeyEnv: "API key \(env var\)"'
|
||||||
|
- --exclude-lines
|
||||||
|
- 'case apikey = "apiKey"'
|
||||||
|
- --exclude-lines
|
||||||
|
- '"gateway\.remote\.password"'
|
||||||
|
- --exclude-lines
|
||||||
|
- '"gateway\.auth\.password"'
|
||||||
|
- --exclude-lines
|
||||||
|
- '"talk\.apiKey"'
|
||||||
|
- --exclude-lines
|
||||||
|
- '=== "string"'
|
||||||
|
- --exclude-lines
|
||||||
|
- 'typeof remote\?\.password === "string"'
|
||||||
|
|
||||||
|
# Shell script linting
|
||||||
|
- repo: https://github.com/koalaman/shellcheck-precommit
|
||||||
|
rev: v0.11.0
|
||||||
|
hooks:
|
||||||
|
- id: shellcheck
|
||||||
|
args: [--severity=error] # Only fail on errors, not warnings/info
|
||||||
|
# Exclude vendor and scripts with embedded code or known issues
|
||||||
|
exclude: '^(vendor/|scripts/e2e/)'
|
||||||
|
|
||||||
|
# GitHub Actions linting
|
||||||
|
- repo: https://github.com/rhysd/actionlint
|
||||||
|
rev: v1.7.10
|
||||||
|
hooks:
|
||||||
|
- id: actionlint
|
||||||
|
|
||||||
|
# GitHub Actions security audit
|
||||||
|
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
||||||
|
rev: v1.22.0
|
||||||
|
hooks:
|
||||||
|
- id: zizmor
|
||||||
|
args: [--persona=regular, --min-severity=medium, --min-confidence=medium]
|
||||||
|
exclude: '^(vendor/|Swabble/)'
|
||||||
|
|
||||||
|
# Project checks (same commands as CI)
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
# oxlint --type-aware src test
|
||||||
|
- id: oxlint
|
||||||
|
name: oxlint
|
||||||
|
entry: scripts/pre-commit/run-node-tool.sh oxlint --type-aware src test
|
||||||
|
language: system
|
||||||
|
pass_filenames: false
|
||||||
|
types_or: [javascript, jsx, ts, tsx]
|
||||||
|
|
||||||
|
# oxfmt --check src test
|
||||||
|
- id: oxfmt
|
||||||
|
name: oxfmt
|
||||||
|
entry: scripts/pre-commit/run-node-tool.sh oxfmt --check src test
|
||||||
|
language: system
|
||||||
|
pass_filenames: false
|
||||||
|
types_or: [javascript, jsx, ts, tsx]
|
||||||
|
|
||||||
|
# swiftlint (same as CI)
|
||||||
|
- id: swiftlint
|
||||||
|
name: swiftlint
|
||||||
|
entry: swiftlint --config .swiftlint.yml
|
||||||
|
language: system
|
||||||
|
pass_filenames: false
|
||||||
|
types: [swift]
|
||||||
|
|
||||||
|
# swiftformat --lint (same as CI)
|
||||||
|
- id: swiftformat
|
||||||
|
name: swiftformat
|
||||||
|
entry: swiftformat --lint apps/macos/Sources --config .swiftformat
|
||||||
|
language: system
|
||||||
|
pass_filenames: false
|
||||||
|
types: [swift]
|
||||||
1923
.secrets.baseline
1923
.secrets.baseline
File diff suppressed because it is too large
Load Diff
25
.shellcheckrc
Normal file
25
.shellcheckrc
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# ShellCheck configuration
|
||||||
|
# https://www.shellcheck.net/wiki/
|
||||||
|
|
||||||
|
# Disable common false positives and style suggestions
|
||||||
|
|
||||||
|
# SC2034: Variable appears unused (often exported or used indirectly)
|
||||||
|
disable=SC2034
|
||||||
|
|
||||||
|
# SC2155: Declare and assign separately (common idiom, rarely causes issues)
|
||||||
|
disable=SC2155
|
||||||
|
|
||||||
|
# SC2295: Expansions inside ${..} need quoting (info-level, rarely causes issues)
|
||||||
|
disable=SC2295
|
||||||
|
|
||||||
|
# SC1012: \r is literal (tr -d '\r' works as intended on most systems)
|
||||||
|
disable=SC1012
|
||||||
|
|
||||||
|
# SC2026: Word outside quotes (info-level, often intentional)
|
||||||
|
disable=SC2026
|
||||||
|
|
||||||
|
# SC2016: Expressions don't expand in single quotes (often intentional in sed/awk)
|
||||||
|
disable=SC2016
|
||||||
|
|
||||||
|
# SC2129: Consider using { cmd1; cmd2; } >> file (style preference)
|
||||||
|
disable=SC2129
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
# Whitespace
|
# Whitespace
|
||||||
--trimwhitespace always
|
--trimwhitespace always
|
||||||
--emptybraces no-space
|
--emptybraces no-space
|
||||||
--nospaceoperators ...,..<
|
--nospaceoperators ...,..<
|
||||||
--ranges no-space
|
--ranges no-space
|
||||||
--someAny true
|
--someAny true
|
||||||
--voidtype void
|
--voidtype void
|
||||||
|
|||||||
@@ -13,11 +13,13 @@
|
|||||||
- Core channel docs: `docs/channels/`
|
- Core channel docs: `docs/channels/`
|
||||||
- Core channel code: `src/telegram`, `src/discord`, `src/slack`, `src/signal`, `src/imessage`, `src/web` (WhatsApp web), `src/channels`, `src/routing`
|
- Core channel code: `src/telegram`, `src/discord`, `src/slack`, `src/signal`, `src/imessage`, `src/web` (WhatsApp web), `src/channels`, `src/routing`
|
||||||
- Extensions (channel plugins): `extensions/*` (e.g. `extensions/msteams`, `extensions/matrix`, `extensions/zalo`, `extensions/zalouser`, `extensions/voice-call`)
|
- Extensions (channel plugins): `extensions/*` (e.g. `extensions/msteams`, `extensions/matrix`, `extensions/zalo`, `extensions/zalouser`, `extensions/voice-call`)
|
||||||
|
- When adding channels/extensions/apps/docs, review `.github/labeler.yml` for label coverage.
|
||||||
|
|
||||||
## Docs Linking (Mintlify)
|
## Docs Linking (Mintlify)
|
||||||
- Docs are hosted on Mintlify (docs.clawd.bot).
|
- Docs are hosted on Mintlify (docs.clawd.bot).
|
||||||
- Internal doc links in `docs/**/*.md`: root-relative, no `.md`/`.mdx` (example: `[Config](/configuration)`).
|
- Internal doc links in `docs/**/*.md`: root-relative, no `.md`/`.mdx` (example: `[Config](/configuration)`).
|
||||||
- Section cross-references: use anchors on root-relative paths (example: `[Hooks](/configuration#hooks)`).
|
- Section cross-references: use anchors on root-relative paths (example: `[Hooks](/configuration#hooks)`).
|
||||||
|
- Doc headings and anchors: avoid em dashes and apostrophes in headings because they break Mintlify anchor links.
|
||||||
- When Peter asks for links, reply with full `https://docs.clawd.bot/...` URLs (not root-relative).
|
- When Peter asks for links, reply with full `https://docs.clawd.bot/...` URLs (not root-relative).
|
||||||
- When you touch docs, end the reply with the `https://docs.clawd.bot/...` URLs you referenced.
|
- When you touch docs, end the reply with the `https://docs.clawd.bot/...` URLs you referenced.
|
||||||
- README (GitHub): keep absolute docs URLs (`https://docs.clawd.bot/...`) so links work on GitHub.
|
- README (GitHub): keep absolute docs URLs (`https://docs.clawd.bot/...`) so links work on GitHub.
|
||||||
@@ -36,6 +38,7 @@
|
|||||||
## Build, Test, and Development Commands
|
## Build, Test, and Development Commands
|
||||||
- Runtime baseline: Node **22+** (keep Node + Bun paths working).
|
- Runtime baseline: Node **22+** (keep Node + Bun paths working).
|
||||||
- Install deps: `pnpm install`
|
- Install deps: `pnpm install`
|
||||||
|
- Pre-commit hooks: `prek install` (runs same checks as CI)
|
||||||
- Also supported: `bun install` (keep `pnpm-lock.yaml` + Bun patching in sync when touching deps/patches).
|
- Also supported: `bun install` (keep `pnpm-lock.yaml` + Bun patching in sync when touching deps/patches).
|
||||||
- Prefer Bun for TypeScript execution (scripts, dev, tests): `bun <file.ts>` / `bunx <tool>`.
|
- Prefer Bun for TypeScript execution (scripts, dev, tests): `bun <file.ts>` / `bunx <tool>`.
|
||||||
- Run CLI in dev: `pnpm clawdbot ...` (bun) or `pnpm dev`.
|
- Run CLI in dev: `pnpm clawdbot ...` (bun) or `pnpm dev`.
|
||||||
|
|||||||
131
CHANGELOG.md
131
CHANGELOG.md
@@ -2,32 +2,143 @@
|
|||||||
|
|
||||||
Docs: https://docs.clawd.bot
|
Docs: https://docs.clawd.bot
|
||||||
|
|
||||||
|
## 2026.1.25
|
||||||
|
Status: unreleased.
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Agents: honor tools.exec.safeBins in exec allowlist checks. (#2281)
|
||||||
|
- Docs: tighten Fly private deployment steps. (#2289) Thanks @dguido.
|
||||||
|
- Gateway: warn on hook tokens via query params; document header auth preference. (#2200) Thanks @YuriNachos.
|
||||||
|
- Gateway: add dangerous Control UI device auth bypass flag + audit warnings. (#2248)
|
||||||
|
- Doctor: warn on gateway exposure without auth. (#2016) Thanks @Alex-Alaniz.
|
||||||
|
- Discord: add configurable privileged gateway intents for presences/members. (#2266) Thanks @kentaro.
|
||||||
|
- Docs: add Vercel AI Gateway to providers sidebar. (#1901) Thanks @jerilynzheng.
|
||||||
|
- Agents: expand cron tool description with full schema docs. (#1988) Thanks @tomascupr.
|
||||||
|
- Skills: add missing dependency metadata for GitHub, Notion, Slack, Discord. (#1995) Thanks @jackheuberger.
|
||||||
|
- Docs: add Render deployment guide. (#1975) Thanks @anurag.
|
||||||
|
- Docs: add Claude Max API Proxy guide. (#1875) Thanks @atalovesyou.
|
||||||
|
- Docs: add DigitalOcean deployment guide. (#1870) Thanks @0xJonHoldsCrypto.
|
||||||
|
- Docs: add Raspberry Pi install guide. (#1871) Thanks @0xJonHoldsCrypto.
|
||||||
|
- Docs: add GCP Compute Engine deployment guide. (#1848) Thanks @hougangdev.
|
||||||
|
- Docs: add LINE channel guide. Thanks @thewilloftheshadow.
|
||||||
|
- Docs: credit both contributors for Control UI refresh. (#1852) Thanks @EnzeD.
|
||||||
|
- Onboarding: add Venice API key to non-interactive flow. (#1893) Thanks @jonisjongithub.
|
||||||
|
- Onboarding: strengthen security warning copy for beta + access control expectations.
|
||||||
|
- Tlon: format thread reply IDs as @ud. (#1837) Thanks @wca4a.
|
||||||
|
- Gateway: prefer newest session metadata when combining stores. (#1823) Thanks @emanuelst.
|
||||||
|
- Web UI: keep sub-agent announce replies visible in WebChat. (#1977) Thanks @andrescardonas7.
|
||||||
|
- CI: increase Node heap size for macOS checks. (#1890) Thanks @realZachi.
|
||||||
|
- macOS: avoid crash when rendering code blocks by bumping Textual to 0.3.1. (#2033) Thanks @garricn.
|
||||||
|
- Browser: fall back to URL matching for extension relay target resolution. (#1999) Thanks @jonit-dev.
|
||||||
|
- Update: ignore dist/control-ui for dirty checks and restore after ui builds. (#1976) Thanks @Glucksberg.
|
||||||
|
- Telegram: allow caption param for media sends. (#1888) Thanks @mguellsegarra.
|
||||||
|
- Telegram: support plugin sendPayload channelData (media/buttons) and validate plugin commands. (#1917) Thanks @JoshuaLelon.
|
||||||
|
- Telegram: avoid block replies when streaming is disabled. (#1885) Thanks @ivancasco.
|
||||||
|
- Auth: show copyable Google auth URL after ASCII prompt. (#1787) Thanks @robbyczgw-cla.
|
||||||
|
- Routing: precompile session key regexes. (#1697) Thanks @Ray0907.
|
||||||
|
- TUI: avoid width overflow when rendering selection lists. (#1686) Thanks @mossein.
|
||||||
|
- Telegram: keep topic IDs in restart sentinel notifications. (#1807) Thanks @hsrvc.
|
||||||
|
- Config: apply config.env before ${VAR} substitution. (#1813) Thanks @spanishflu-est1918.
|
||||||
|
- Slack: clear ack reaction after streamed replies. (#2044) Thanks @fancyboi999.
|
||||||
|
- macOS: keep custom SSH usernames in remote target. (#2046) Thanks @algal.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Telegram: wrap reasoning italics per line to avoid raw underscores. (#2181) Thanks @YuriNachos.
|
||||||
|
- Voice Call: enforce Twilio webhook signature verification for ngrok URLs; disable ngrok free tier bypass by default.
|
||||||
|
- Security: harden Tailscale Serve auth by validating identity via local tailscaled before trusting headers.
|
||||||
|
- Build: align memory-core peer dependency with lockfile.
|
||||||
|
- Security: add mDNS discovery mode with minimal default to reduce information disclosure. (#1882) Thanks @orlyjamie.
|
||||||
|
- Security: harden URL fetches with DNS pinning to reduce rebinding risk. Thanks Chris Zheng.
|
||||||
|
- Web UI: improve WebChat image paste previews and allow image-only sends. (#1925) Thanks @smartprogrammer93.
|
||||||
|
- Security: wrap external hook content by default with a per-hook opt-out. (#1827) Thanks @mertcicekci0.
|
||||||
|
- Gateway: default auth now fail-closed (token/password required; Tailscale Serve identity remains allowed).
|
||||||
|
- Onboarding: remove unsupported gateway auth "off" choice from onboarding/configure flows and CLI flags.
|
||||||
|
|
||||||
|
## 2026.1.24-3
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Slack: fix image downloads failing due to missing Authorization header on cross-origin redirects. (#1936) Thanks @sanderhelgesen.
|
||||||
|
- Gateway: harden reverse proxy handling for local-client detection and unauthenticated proxied connects. (#1795) Thanks @orlyjamie.
|
||||||
|
- Security audit: flag loopback Control UI with auth disabled as critical. (#1795) Thanks @orlyjamie.
|
||||||
|
- CLI: resume claude-cli sessions and stream CLI replies to TUI clients. (#1921) Thanks @rmorse.
|
||||||
|
|
||||||
|
## 2026.1.24-2
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Packaging: include dist/link-understanding output in npm tarball (fixes missing apply.js import on install).
|
||||||
|
|
||||||
|
## 2026.1.24-1
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Packaging: include dist/shared output in npm tarball (fixes missing reasoning-tags import on install).
|
||||||
|
|
||||||
## 2026.1.24
|
## 2026.1.24
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
- Ollama: provider discovery + docs. (#1606) Thanks @abhaymundhara. https://docs.clawd.bot/providers/ollama
|
- Providers: Ollama discovery + docs; Venice guide upgrades + cross-links. (#1606) Thanks @abhaymundhara. https://docs.clawd.bot/providers/ollama https://docs.clawd.bot/providers/venice
|
||||||
|
- Channels: LINE plugin (Messaging API) with rich replies + quick replies. (#1630) Thanks @plum-dawg.
|
||||||
|
- TTS: Edge fallback (keyless) + `/tts` auto modes. (#1668, #1667) Thanks @steipete, @sebslight. https://docs.clawd.bot/tts
|
||||||
|
- Exec approvals: approve in-chat via `/approve` across all channels (including plugins). (#1621) Thanks @czekaj. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/slash-commands
|
||||||
|
- Telegram: DM topics as separate sessions + outbound link preview toggle. (#1597, #1700) Thanks @rohannagpal, @zerone0x. https://docs.clawd.bot/channels/telegram
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- Docs: expand FAQ (migration, scheduling, concurrency, model recommendations, OpenAI subscription auth, Pi sizing, hackable install, docs SSL workaround).
|
- Channels: add LINE plugin (Messaging API) with rich replies, quick replies, and plugin HTTP registry. (#1630) Thanks @plum-dawg.
|
||||||
- Docs: add verbose installer troubleshooting guidance.
|
- TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.clawd.bot/tts
|
||||||
- Docs: update Fly.io guide notes.
|
- TTS: add auto mode enum (off/always/inbound/tagged) with per-session `/tts` override. (#1667) Thanks @sebslight. https://docs.clawd.bot/tts
|
||||||
- Docs: add Bedrock EC2 instance role setup + IAM steps. (#1625) Thanks @sergical. https://docs.clawd.bot/bedrock
|
- Telegram: treat DM topics as separate sessions and keep DM history limits stable with thread suffixes. (#1597) Thanks @rohannagpal.
|
||||||
|
- Telegram: add `channels.telegram.linkPreview` to toggle outbound link previews. (#1700) Thanks @zerone0x. https://docs.clawd.bot/channels/telegram
|
||||||
|
- Web search: add Brave freshness filter parameter for time-scoped results. (#1688) Thanks @JonUleis. https://docs.clawd.bot/tools/web
|
||||||
|
- UI: refresh Control UI dashboard design system (colors, icons, typography). (#1745, #1786) Thanks @EnzeD, @mousberg.
|
||||||
- Exec approvals: forward approval prompts to chat with `/approve` for all channels (including plugins). (#1621) Thanks @czekaj. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/slash-commands
|
- Exec approvals: forward approval prompts to chat with `/approve` for all channels (including plugins). (#1621) Thanks @czekaj. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/slash-commands
|
||||||
- Gateway: expose config.patch in the gateway tool with safe partial updates + restart sentinel. (#1653) Thanks @Glucksberg.
|
- Gateway: expose config.patch in the gateway tool with safe partial updates + restart sentinel. (#1653) Thanks @Glucksberg.
|
||||||
|
- Diagnostics: add diagnostic flags for targeted debug logs (config + env override). https://docs.clawd.bot/diagnostics/flags
|
||||||
|
- Docs: expand FAQ (migration, scheduling, concurrency, model recommendations, OpenAI subscription auth, Pi sizing, hackable install, docs SSL workaround).
|
||||||
|
- Docs: add verbose installer troubleshooting guidance.
|
||||||
|
- Docs: add macOS VM guide with local/hosted options + VPS/nodes guidance. (#1693) Thanks @f-trycua.
|
||||||
|
- Docs: add Bedrock EC2 instance role setup + IAM steps. (#1625) Thanks @sergical. https://docs.clawd.bot/bedrock
|
||||||
|
- Docs: update Fly.io guide notes.
|
||||||
|
- Dev: add prek pre-commit hooks + dependabot config for weekly updates. (#1720) Thanks @dguido.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- BlueBubbles: keep part-index GUIDs in reply tags when short IDs are missing.
|
- Web UI: fix config/debug layout overflow, scrolling, and code block sizing. (#1715) Thanks @saipreetham589.
|
||||||
|
- Web UI: show Stop button during active runs, swap back to New session when idle. (#1664) Thanks @ndbroadbent.
|
||||||
|
- Web UI: clear stale disconnect banners on reconnect; allow form saves with unsupported schema paths but block missing schema. (#1707) Thanks @Glucksberg.
|
||||||
- Web UI: hide internal `message_id` hints in chat bubbles.
|
- Web UI: hide internal `message_id` hints in chat bubbles.
|
||||||
- Heartbeat: normalize target identifiers for consistent routing.
|
- Gateway: allow Control UI token-only auth to skip device pairing even when device identity is present (`gateway.controlUi.allowInsecureAuth`). (#1679) Thanks @steipete.
|
||||||
|
- Matrix: decrypt E2EE media attachments with preflight size guard. (#1744) Thanks @araa47.
|
||||||
|
- BlueBubbles: route phone-number targets to DMs, avoid leaking routing IDs, and auto-create missing DMs (Private API required). (#1751) Thanks @tyler6204. https://docs.clawd.bot/channels/bluebubbles
|
||||||
|
- BlueBubbles: keep part-index GUIDs in reply tags when short IDs are missing.
|
||||||
|
- iMessage: normalize chat_id/chat_guid/chat_identifier prefixes case-insensitively and keep service-prefixed handles stable. (#1708) Thanks @aaronn.
|
||||||
|
- Signal: repair reaction sends (group/UUID targets + CLI author flags). (#1651) Thanks @vilkasdev.
|
||||||
|
- Signal: add configurable signal-cli startup timeout + external daemon mode docs. (#1677) https://docs.clawd.bot/channels/signal
|
||||||
|
- Telegram: set fetch duplex="half" for uploads on Node 22 to avoid sendPhoto failures. (#1684) Thanks @commdata2338.
|
||||||
- Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639)
|
- Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639)
|
||||||
- Exec: keep approvals for elevated ask unless full mode. (#1616) Thanks @ivancasco.
|
- Telegram: honor per-account proxy for outbound API calls. (#1774) Thanks @radek-paclt.
|
||||||
|
- Telegram: fall back to text when voice notes are blocked by privacy settings. (#1725) Thanks @foeken.
|
||||||
|
- Voice Call: return stream TwiML for outbound conversation calls on initial Twilio webhook. (#1634)
|
||||||
|
- Voice Call: serialize Twilio TTS playback and cancel on barge-in to prevent overlap. (#1713) Thanks @dguido.
|
||||||
|
- Google Chat: tighten email allowlist matching, typing cleanup, media caps, and onboarding/docs/tests. (#1635) Thanks @iHildy.
|
||||||
|
- Google Chat: normalize space targets without double `spaces/` prefix.
|
||||||
- Agents: auto-compact on context overflow prompt errors before failing. (#1627) Thanks @rodrigouroz.
|
- Agents: auto-compact on context overflow prompt errors before failing. (#1627) Thanks @rodrigouroz.
|
||||||
- Agents: use the active auth profile for auto-compaction recovery.
|
- Agents: use the active auth profile for auto-compaction recovery.
|
||||||
|
- Media understanding: skip image understanding when the primary model already supports vision. (#1747) Thanks @tyler6204.
|
||||||
- Models: default missing custom provider fields so minimal configs are accepted.
|
- Models: default missing custom provider fields so minimal configs are accepted.
|
||||||
|
- Messaging: keep newline chunking safe for fenced markdown blocks across channels.
|
||||||
|
- Messaging: treat newline chunking as paragraph-aware (blank-line splits) to keep lists and headings together. (#1726) Thanks @tyler6204.
|
||||||
|
- TUI: reload history after gateway reconnect to restore session state. (#1663)
|
||||||
|
- Heartbeat: normalize target identifiers for consistent routing.
|
||||||
|
- Exec: keep approvals for elevated ask unless full mode. (#1616) Thanks @ivancasco.
|
||||||
|
- Exec: treat Windows platform labels as Windows for node shell selection. (#1760) Thanks @ymat19.
|
||||||
|
- Gateway: include inline config env vars in service install environments. (#1735) Thanks @Seredeep.
|
||||||
|
- Gateway: skip Tailscale DNS probing when tailscale.mode is off. (#1671)
|
||||||
- Gateway: reduce log noise for late invokes + remote node probes; debounce skills refresh. (#1607) Thanks @petter-b.
|
- Gateway: reduce log noise for late invokes + remote node probes; debounce skills refresh. (#1607) Thanks @petter-b.
|
||||||
|
- Gateway: clarify Control UI/WebChat auth error hints for missing tokens. (#1690)
|
||||||
|
- Gateway: listen on IPv6 loopback when bound to 127.0.0.1 so localhost webhooks work.
|
||||||
|
- Gateway: store lock files in the temp directory to avoid stale locks on persistent volumes. (#1676)
|
||||||
- macOS: default direct-transport `ws://` URLs to port 18789; document `gateway.remote.transport`. (#1603) Thanks @ngutman.
|
- macOS: default direct-transport `ws://` URLs to port 18789; document `gateway.remote.transport`. (#1603) Thanks @ngutman.
|
||||||
- Voice Call: return stream TwiML for outbound conversation calls on initial Twilio webhook. (#1634)
|
- Tests: cap Vitest workers on CI macOS to reduce timeouts. (#1597) Thanks @rohannagpal.
|
||||||
- Google Chat: tighten email allowlist matching, typing cleanup, media caps, and onboarding/docs/tests. (#1635) Thanks @iHildy.
|
- Tests: avoid fake-timer dependency in embedded runner stream mock to reduce CI flakes. (#1597) Thanks @rohannagpal.
|
||||||
|
- Tests: increase embedded runner ordering test timeout to reduce CI flakes. (#1597) Thanks @rohannagpal.
|
||||||
|
|
||||||
## 2026.1.23-1
|
## 2026.1.23-1
|
||||||
|
|
||||||
|
|||||||
@@ -40,3 +40,13 @@ Please include in your PR:
|
|||||||
- [ ] Confirm you understand what the code does
|
- [ ] Confirm you understand what the code does
|
||||||
|
|
||||||
AI PRs are first-class citizens here. We just want transparency so reviewers know what to look for.
|
AI PRs are first-class citizens here. We just want transparency so reviewers know what to look for.
|
||||||
|
|
||||||
|
## Current Focus & Roadmap 🗺
|
||||||
|
|
||||||
|
We are currently prioritizing:
|
||||||
|
- **Stability**: Fixing edge cases in channel connections (WhatsApp/Telegram).
|
||||||
|
- **UX**: Improving the onboarding wizard and error messages.
|
||||||
|
- **Skills**: Expanding the library of bundled skills and improving the Skill Creation developer experience.
|
||||||
|
- **Performance**: Optimizing token usage and compaction logic.
|
||||||
|
|
||||||
|
Check the [GitHub Issues](https://github.com/clawdbot/clawdbot/issues) for "good first issue" labels!
|
||||||
|
|||||||
@@ -32,4 +32,9 @@ RUN pnpm ui:build
|
|||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
# Security hardening: Run as non-root user
|
||||||
|
# The node:22-bookworm image includes a 'node' user (uid 1000)
|
||||||
|
# This reduces the attack surface by preventing container escape via root privileges
|
||||||
|
USER node
|
||||||
|
|
||||||
CMD ["node", "dist/index.js"]
|
CMD ["node", "dist/index.js"]
|
||||||
|
|||||||
62
README.md
62
README.md
@@ -459,7 +459,7 @@ Use these when you’re past the onboarding flow and want the deeper reference.
|
|||||||
|
|
||||||
## Clawd
|
## Clawd
|
||||||
|
|
||||||
Clawdbot was built for **Clawd**, a space lobster AI assistant. 🦞
|
Clawdbot was built for **Clawd**, a space lobster AI assistant. 🦞
|
||||||
by Peter Steinberger and the community.
|
by Peter Steinberger and the community.
|
||||||
|
|
||||||
- [clawd.me](https://clawd.me)
|
- [clawd.me](https://clawd.me)
|
||||||
@@ -468,7 +468,7 @@ by Peter Steinberger and the community.
|
|||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines, maintainers, and how to submit PRs.
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines, maintainers, and how to submit PRs.
|
||||||
AI/vibe-coded PRs welcome! 🤖
|
AI/vibe-coded PRs welcome! 🤖
|
||||||
|
|
||||||
Special thanks to [Mario Zechner](https://mariozechner.at/) for his support and for
|
Special thanks to [Mario Zechner](https://mariozechner.at/) for his support and for
|
||||||
@@ -477,31 +477,35 @@ Special thanks to [Mario Zechner](https://mariozechner.at/) for his support and
|
|||||||
Thanks to all clawtributors:
|
Thanks to all clawtributors:
|
||||||
|
|
||||||
<p align="left">
|
<p align="left">
|
||||||
<a href="https://github.com/steipete"><img src="https://avatars.githubusercontent.com/u/58493?v=4&s=48" width="48" height="48" alt="steipete" title="steipete"/></a> <a href="https://github.com/bohdanpodvirnyi"><img src="https://avatars.githubusercontent.com/u/31819391?v=4&s=48" width="48" height="48" alt="bohdanpodvirnyi" title="bohdanpodvirnyi"/></a> <a href="https://github.com/joaohlisboa"><img src="https://avatars.githubusercontent.com/u/8200873?v=4&s=48" width="48" height="48" alt="joaohlisboa" title="joaohlisboa"/></a> <a href="https://github.com/mneves75"><img src="https://avatars.githubusercontent.com/u/2423436?v=4&s=48" width="48" height="48" alt="mneves75" title="mneves75"/></a> <a href="https://github.com/MatthieuBizien"><img src="https://avatars.githubusercontent.com/u/173090?v=4&s=48" width="48" height="48" alt="MatthieuBizien" title="MatthieuBizien"/></a> <a href="https://github.com/MaudeBot"><img src="https://avatars.githubusercontent.com/u/255777700?v=4&s=48" width="48" height="48" alt="MaudeBot" title="MaudeBot"/></a> <a href="https://github.com/rahthakor"><img src="https://avatars.githubusercontent.com/u/8470553?v=4&s=48" width="48" height="48" alt="rahthakor" title="rahthakor"/></a> <a href="https://github.com/vrknetha"><img src="https://avatars.githubusercontent.com/u/20596261?v=4&s=48" width="48" height="48" alt="vrknetha" title="vrknetha"/></a> <a href="https://github.com/radek-paclt"><img src="https://avatars.githubusercontent.com/u/50451445?v=4&s=48" width="48" height="48" alt="radek-paclt" title="radek-paclt"/></a> <a href="https://github.com/tobiasbischoff"><img src="https://avatars.githubusercontent.com/u/711564?v=4&s=48" width="48" height="48" alt="Tobias Bischoff" title="Tobias Bischoff"/></a>
|
<a href="https://github.com/steipete"><img src="https://avatars.githubusercontent.com/u/58493?v=4&s=48" width="48" height="48" alt="steipete" title="steipete"/></a> <a href="https://github.com/plum-dawg"><img src="https://avatars.githubusercontent.com/u/5909950?v=4&s=48" width="48" height="48" alt="plum-dawg" title="plum-dawg"/></a> <a href="https://github.com/bohdanpodvirnyi"><img src="https://avatars.githubusercontent.com/u/31819391?v=4&s=48" width="48" height="48" alt="bohdanpodvirnyi" title="bohdanpodvirnyi"/></a> <a href="https://github.com/iHildy"><img src="https://avatars.githubusercontent.com/u/25069719?v=4&s=48" width="48" height="48" alt="iHildy" title="iHildy"/></a> <a href="https://github.com/joaohlisboa"><img src="https://avatars.githubusercontent.com/u/8200873?v=4&s=48" width="48" height="48" alt="joaohlisboa" title="joaohlisboa"/></a> <a href="https://github.com/mneves75"><img src="https://avatars.githubusercontent.com/u/2423436?v=4&s=48" width="48" height="48" alt="mneves75" title="mneves75"/></a> <a href="https://github.com/MatthieuBizien"><img src="https://avatars.githubusercontent.com/u/173090?v=4&s=48" width="48" height="48" alt="MatthieuBizien" title="MatthieuBizien"/></a> <a href="https://github.com/MaudeBot"><img src="https://avatars.githubusercontent.com/u/255777700?v=4&s=48" width="48" height="48" alt="MaudeBot" title="MaudeBot"/></a> <a href="https://github.com/Glucksberg"><img src="https://avatars.githubusercontent.com/u/80581902?v=4&s=48" width="48" height="48" alt="Glucksberg" title="Glucksberg"/></a> <a href="https://github.com/rahthakor"><img src="https://avatars.githubusercontent.com/u/8470553?v=4&s=48" width="48" height="48" alt="rahthakor" title="rahthakor"/></a>
|
||||||
<a href="https://github.com/joshp123"><img src="https://avatars.githubusercontent.com/u/1497361?v=4&s=48" width="48" height="48" alt="joshp123" title="joshp123"/></a> <a href="https://github.com/mukhtharcm"><img src="https://avatars.githubusercontent.com/u/56378562?v=4&s=48" width="48" height="48" alt="mukhtharcm" title="mukhtharcm"/></a> <a href="https://github.com/maxsumrall"><img src="https://avatars.githubusercontent.com/u/628843?v=4&s=48" width="48" height="48" alt="maxsumrall" title="maxsumrall"/></a> <a href="https://github.com/xadenryan"><img src="https://avatars.githubusercontent.com/u/165437834?v=4&s=48" width="48" height="48" alt="xadenryan" title="xadenryan"/></a> <a href="https://github.com/juanpablodlc"><img src="https://avatars.githubusercontent.com/u/92012363?v=4&s=48" width="48" height="48" alt="juanpablodlc" title="juanpablodlc"/></a> <a href="https://github.com/hsrvc"><img src="https://avatars.githubusercontent.com/u/129702169?v=4&s=48" width="48" height="48" alt="hsrvc" title="hsrvc"/></a> <a href="https://github.com/magimetal"><img src="https://avatars.githubusercontent.com/u/36491250?v=4&s=48" width="48" height="48" alt="magimetal" title="magimetal"/></a> <a href="https://github.com/meaningfool"><img src="https://avatars.githubusercontent.com/u/2862331?v=4&s=48" width="48" height="48" alt="meaningfool" title="meaningfool"/></a> <a href="https://github.com/patelhiren"><img src="https://avatars.githubusercontent.com/u/172098?v=4&s=48" width="48" height="48" alt="patelhiren" title="patelhiren"/></a> <a href="https://github.com/NicholasSpisak"><img src="https://avatars.githubusercontent.com/u/129075147?v=4&s=48" width="48" height="48" alt="NicholasSpisak" title="NicholasSpisak"/></a>
|
<a href="https://github.com/vrknetha"><img src="https://avatars.githubusercontent.com/u/20596261?v=4&s=48" width="48" height="48" alt="vrknetha" title="vrknetha"/></a> <a href="https://github.com/radek-paclt"><img src="https://avatars.githubusercontent.com/u/50451445?v=4&s=48" width="48" height="48" alt="radek-paclt" title="radek-paclt"/></a> <a href="https://github.com/tobiasbischoff"><img src="https://avatars.githubusercontent.com/u/711564?v=4&s=48" width="48" height="48" alt="Tobias Bischoff" title="Tobias Bischoff"/></a> <a href="https://github.com/joshp123"><img src="https://avatars.githubusercontent.com/u/1497361?v=4&s=48" width="48" height="48" alt="joshp123" title="joshp123"/></a> <a href="https://github.com/czekaj"><img src="https://avatars.githubusercontent.com/u/1464539?v=4&s=48" width="48" height="48" alt="czekaj" title="czekaj"/></a> <a href="https://github.com/mukhtharcm"><img src="https://avatars.githubusercontent.com/u/56378562?v=4&s=48" width="48" height="48" alt="mukhtharcm" title="mukhtharcm"/></a> <a href="https://github.com/sebslight"><img src="https://avatars.githubusercontent.com/u/19554889?v=4&s=48" width="48" height="48" alt="sebslight" title="sebslight"/></a> <a href="https://github.com/maxsumrall"><img src="https://avatars.githubusercontent.com/u/628843?v=4&s=48" width="48" height="48" alt="maxsumrall" title="maxsumrall"/></a> <a href="https://github.com/xadenryan"><img src="https://avatars.githubusercontent.com/u/165437834?v=4&s=48" width="48" height="48" alt="xadenryan" title="xadenryan"/></a> <a href="https://github.com/rodrigouroz"><img src="https://avatars.githubusercontent.com/u/384037?v=4&s=48" width="48" height="48" alt="rodrigouroz" title="rodrigouroz"/></a>
|
||||||
<a href="https://github.com/sebslight"><img src="https://avatars.githubusercontent.com/u/19554889?v=4&s=48" width="48" height="48" alt="sebslight" title="sebslight"/></a> <a href="https://github.com/AbhisekBasu1"><img src="https://avatars.githubusercontent.com/u/40645221?v=4&s=48" width="48" height="48" alt="abhisekbasu1" title="abhisekbasu1"/></a> <a href="https://github.com/zerone0x"><img src="https://avatars.githubusercontent.com/u/39543393?v=4&s=48" width="48" height="48" alt="zerone0x" title="zerone0x"/></a> <a href="https://github.com/jamesgroat"><img src="https://avatars.githubusercontent.com/u/2634024?v=4&s=48" width="48" height="48" alt="jamesgroat" title="jamesgroat"/></a> <a href="https://github.com/claude"><img src="https://avatars.githubusercontent.com/u/81847?v=4&s=48" width="48" height="48" alt="claude" title="claude"/></a> <a href="https://github.com/JustYannicc"><img src="https://avatars.githubusercontent.com/u/52761674?v=4&s=48" width="48" height="48" alt="JustYannicc" title="JustYannicc"/></a> <a href="https://github.com/SocialNerd42069"><img src="https://avatars.githubusercontent.com/u/118244303?v=4&s=48" width="48" height="48" alt="SocialNerd42069" title="SocialNerd42069"/></a> <a href="https://github.com/Hyaxia"><img src="https://avatars.githubusercontent.com/u/36747317?v=4&s=48" width="48" height="48" alt="Hyaxia" title="Hyaxia"/></a> <a href="https://github.com/dantelex"><img src="https://avatars.githubusercontent.com/u/631543?v=4&s=48" width="48" height="48" alt="dantelex" title="dantelex"/></a> <a href="https://github.com/daveonkels"><img src="https://avatars.githubusercontent.com/u/533642?v=4&s=48" width="48" height="48" alt="daveonkels" title="daveonkels"/></a>
|
<a href="https://github.com/juanpablodlc"><img src="https://avatars.githubusercontent.com/u/92012363?v=4&s=48" width="48" height="48" alt="juanpablodlc" title="juanpablodlc"/></a> <a href="https://github.com/hsrvc"><img src="https://avatars.githubusercontent.com/u/129702169?v=4&s=48" width="48" height="48" alt="hsrvc" title="hsrvc"/></a> <a href="https://github.com/magimetal"><img src="https://avatars.githubusercontent.com/u/36491250?v=4&s=48" width="48" height="48" alt="magimetal" title="magimetal"/></a> <a href="https://github.com/zerone0x"><img src="https://avatars.githubusercontent.com/u/39543393?v=4&s=48" width="48" height="48" alt="zerone0x" title="zerone0x"/></a> <a href="https://github.com/meaningfool"><img src="https://avatars.githubusercontent.com/u/2862331?v=4&s=48" width="48" height="48" alt="meaningfool" title="meaningfool"/></a> <a href="https://github.com/tyler6204"><img src="https://avatars.githubusercontent.com/u/64381258?v=4&s=48" width="48" height="48" alt="tyler6204" title="tyler6204"/></a> <a href="https://github.com/patelhiren"><img src="https://avatars.githubusercontent.com/u/172098?v=4&s=48" width="48" height="48" alt="patelhiren" title="patelhiren"/></a> <a href="https://github.com/NicholasSpisak"><img src="https://avatars.githubusercontent.com/u/129075147?v=4&s=48" width="48" height="48" alt="NicholasSpisak" title="NicholasSpisak"/></a> <a href="https://github.com/jonisjongithub"><img src="https://avatars.githubusercontent.com/u/86072337?v=4&s=48" width="48" height="48" alt="jonisjongithub" title="jonisjongithub"/></a> <a href="https://github.com/AbhisekBasu1"><img src="https://avatars.githubusercontent.com/u/40645221?v=4&s=48" width="48" height="48" alt="abhisekbasu1" title="abhisekbasu1"/></a>
|
||||||
<a href="https://github.com/Glucksberg"><img src="https://avatars.githubusercontent.com/u/80581902?v=4&s=48" width="48" height="48" alt="Glucksberg" title="Glucksberg"/></a> <a href="https://github.com/apps/google-labs-jules"><img src="https://avatars.githubusercontent.com/in/842251?v=4&s=48" width="48" height="48" alt="google-labs-jules[bot]" title="google-labs-jules[bot]"/></a> <a href="https://github.com/vignesh07"><img src="https://avatars.githubusercontent.com/u/1436853?v=4&s=48" width="48" height="48" alt="vignesh07" title="vignesh07"/></a> <a href="https://github.com/mteam88"><img src="https://avatars.githubusercontent.com/u/84196639?v=4&s=48" width="48" height="48" alt="mteam88" title="mteam88"/></a> <a href="https://github.com/omniwired"><img src="https://avatars.githubusercontent.com/u/322761?v=4&s=48" width="48" height="48" alt="Eng. Juan Combetto" title="Eng. Juan Combetto"/></a> <a href="https://github.com/mbelinky"><img src="https://avatars.githubusercontent.com/u/132747814?v=4&s=48" width="48" height="48" alt="Mariano Belinky" title="Mariano Belinky"/></a> <a href="https://github.com/dbhurley"><img src="https://avatars.githubusercontent.com/u/5251425?v=4&s=48" width="48" height="48" alt="dbhurley" title="dbhurley"/></a> <a href="https://github.com/TSavo"><img src="https://avatars.githubusercontent.com/u/877990?v=4&s=48" width="48" height="48" alt="TSavo" title="TSavo"/></a> <a href="https://github.com/julianengel"><img src="https://avatars.githubusercontent.com/u/10634231?v=4&s=48" width="48" height="48" alt="julianengel" title="julianengel"/></a> <a href="https://github.com/benithors"><img src="https://avatars.githubusercontent.com/u/20652882?v=4&s=48" width="48" height="48" alt="benithors" title="benithors"/></a>
|
<a href="https://github.com/jamesgroat"><img src="https://avatars.githubusercontent.com/u/2634024?v=4&s=48" width="48" height="48" alt="jamesgroat" title="jamesgroat"/></a> <a href="https://github.com/claude"><img src="https://avatars.githubusercontent.com/u/81847?v=4&s=48" width="48" height="48" alt="claude" title="claude"/></a> <a href="https://github.com/JustYannicc"><img src="https://avatars.githubusercontent.com/u/52761674?v=4&s=48" width="48" height="48" alt="JustYannicc" title="JustYannicc"/></a> <a href="https://github.com/Hyaxia"><img src="https://avatars.githubusercontent.com/u/36747317?v=4&s=48" width="48" height="48" alt="Hyaxia" title="Hyaxia"/></a> <a href="https://github.com/dantelex"><img src="https://avatars.githubusercontent.com/u/631543?v=4&s=48" width="48" height="48" alt="dantelex" title="dantelex"/></a> <a href="https://github.com/SocialNerd42069"><img src="https://avatars.githubusercontent.com/u/118244303?v=4&s=48" width="48" height="48" alt="SocialNerd42069" title="SocialNerd42069"/></a> <a href="https://github.com/daveonkels"><img src="https://avatars.githubusercontent.com/u/533642?v=4&s=48" width="48" height="48" alt="daveonkels" title="daveonkels"/></a> <a href="https://github.com/apps/google-labs-jules"><img src="https://avatars.githubusercontent.com/in/842251?v=4&s=48" width="48" height="48" alt="google-labs-jules[bot]" title="google-labs-jules[bot]"/></a> <a href="https://github.com/lc0rp"><img src="https://avatars.githubusercontent.com/u/2609441?v=4&s=48" width="48" height="48" alt="lc0rp" title="lc0rp"/></a> <a href="https://github.com/mousberg"><img src="https://avatars.githubusercontent.com/u/57605064?v=4&s=48" width="48" height="48" alt="mousberg" title="mousberg"/></a>
|
||||||
<a href="https://github.com/bradleypriest"><img src="https://avatars.githubusercontent.com/u/167215?v=4&s=48" width="48" height="48" alt="bradleypriest" title="bradleypriest"/></a> <a href="https://github.com/timolins"><img src="https://avatars.githubusercontent.com/u/1440854?v=4&s=48" width="48" height="48" alt="timolins" title="timolins"/></a> <a href="https://github.com/Nachx639"><img src="https://avatars.githubusercontent.com/u/71144023?v=4&s=48" width="48" height="48" alt="nachx639" title="nachx639"/></a> <a href="https://github.com/pvoo"><img src="https://avatars.githubusercontent.com/u/20116814?v=4&s=48" width="48" height="48" alt="pvoo" title="pvoo"/></a> <a href="https://github.com/sreekaransrinath"><img src="https://avatars.githubusercontent.com/u/50989977?v=4&s=48" width="48" height="48" alt="sreekaransrinath" title="sreekaransrinath"/></a> <a href="https://github.com/gupsammy"><img src="https://avatars.githubusercontent.com/u/20296019?v=4&s=48" width="48" height="48" alt="gupsammy" title="gupsammy"/></a> <a href="https://github.com/cristip73"><img src="https://avatars.githubusercontent.com/u/24499421?v=4&s=48" width="48" height="48" alt="cristip73" title="cristip73"/></a> <a href="https://github.com/stefangalescu"><img src="https://avatars.githubusercontent.com/u/52995748?v=4&s=48" width="48" height="48" alt="stefangalescu" title="stefangalescu"/></a> <a href="https://github.com/nachoiacovino"><img src="https://avatars.githubusercontent.com/u/50103937?v=4&s=48" width="48" height="48" alt="nachoiacovino" title="nachoiacovino"/></a> <a href="https://github.com/vsabavat"><img src="https://avatars.githubusercontent.com/u/50385532?v=4&s=48" width="48" height="48" alt="Vasanth Rao Naik Sabavat" title="Vasanth Rao Naik Sabavat"/></a>
|
<a href="https://github.com/vignesh07"><img src="https://avatars.githubusercontent.com/u/1436853?v=4&s=48" width="48" height="48" alt="vignesh07" title="vignesh07"/></a> <a href="https://github.com/mteam88"><img src="https://avatars.githubusercontent.com/u/84196639?v=4&s=48" width="48" height="48" alt="mteam88" title="mteam88"/></a> <a href="https://github.com/joeynyc"><img src="https://avatars.githubusercontent.com/u/17919866?v=4&s=48" width="48" height="48" alt="joeynyc" title="joeynyc"/></a> <a href="https://github.com/orlyjamie"><img src="https://avatars.githubusercontent.com/u/6668807?v=4&s=48" width="48" height="48" alt="orlyjamie" title="orlyjamie"/></a> <a href="https://github.com/dbhurley"><img src="https://avatars.githubusercontent.com/u/5251425?v=4&s=48" width="48" height="48" alt="dbhurley" title="dbhurley"/></a> <a href="https://github.com/mbelinky"><img src="https://avatars.githubusercontent.com/u/132747814?v=4&s=48" width="48" height="48" alt="Mariano Belinky" title="Mariano Belinky"/></a> <a href="https://github.com/omniwired"><img src="https://avatars.githubusercontent.com/u/322761?v=4&s=48" width="48" height="48" alt="Eng. Juan Combetto" title="Eng. Juan Combetto"/></a> <a href="https://github.com/TSavo"><img src="https://avatars.githubusercontent.com/u/877990?v=4&s=48" width="48" height="48" alt="TSavo" title="TSavo"/></a> <a href="https://github.com/julianengel"><img src="https://avatars.githubusercontent.com/u/10634231?v=4&s=48" width="48" height="48" alt="julianengel" title="julianengel"/></a> <a href="https://github.com/bradleypriest"><img src="https://avatars.githubusercontent.com/u/167215?v=4&s=48" width="48" height="48" alt="bradleypriest" title="bradleypriest"/></a>
|
||||||
<a href="https://github.com/iHildy"><img src="https://avatars.githubusercontent.com/u/25069719?v=4&s=48" width="48" height="48" alt="iHildy" title="iHildy"/></a> <a href="https://github.com/cpojer"><img src="https://avatars.githubusercontent.com/u/13352?v=4&s=48" width="48" height="48" alt="cpojer" title="cpojer"/></a> <a href="https://github.com/lc0rp"><img src="https://avatars.githubusercontent.com/u/2609441?v=4&s=48" width="48" height="48" alt="lc0rp" title="lc0rp"/></a> <a href="https://github.com/scald"><img src="https://avatars.githubusercontent.com/u/1215913?v=4&s=48" width="48" height="48" alt="scald" title="scald"/></a> <a href="https://github.com/gumadeiras"><img src="https://avatars.githubusercontent.com/u/5599352?v=4&s=48" width="48" height="48" alt="gumadeiras" title="gumadeiras"/></a> <a href="https://github.com/andranik-sahakyan"><img src="https://avatars.githubusercontent.com/u/8908029?v=4&s=48" width="48" height="48" alt="andranik-sahakyan" title="andranik-sahakyan"/></a> <a href="https://github.com/davidguttman"><img src="https://avatars.githubusercontent.com/u/431696?v=4&s=48" width="48" height="48" alt="davidguttman" title="davidguttman"/></a> <a href="https://github.com/sleontenko"><img src="https://avatars.githubusercontent.com/u/7135949?v=4&s=48" width="48" height="48" alt="sleontenko" title="sleontenko"/></a> <a href="https://github.com/rodrigouroz"><img src="https://avatars.githubusercontent.com/u/384037?v=4&s=48" width="48" height="48" alt="rodrigouroz" title="rodrigouroz"/></a> <a href="https://github.com/sircrumpet"><img src="https://avatars.githubusercontent.com/u/4436535?v=4&s=48" width="48" height="48" alt="sircrumpet" title="sircrumpet"/></a>
|
<a href="https://github.com/benithors"><img src="https://avatars.githubusercontent.com/u/20652882?v=4&s=48" width="48" height="48" alt="benithors" title="benithors"/></a> <a href="https://github.com/rohannagpal"><img src="https://avatars.githubusercontent.com/u/4009239?v=4&s=48" width="48" height="48" alt="rohannagpal" title="rohannagpal"/></a> <a href="https://github.com/timolins"><img src="https://avatars.githubusercontent.com/u/1440854?v=4&s=48" width="48" height="48" alt="timolins" title="timolins"/></a> <a href="https://github.com/f-trycua"><img src="https://avatars.githubusercontent.com/u/195596869?v=4&s=48" width="48" height="48" alt="f-trycua" title="f-trycua"/></a> <a href="https://github.com/benostein"><img src="https://avatars.githubusercontent.com/u/31802821?v=4&s=48" width="48" height="48" alt="benostein" title="benostein"/></a> <a href="https://github.com/Nachx639"><img src="https://avatars.githubusercontent.com/u/71144023?v=4&s=48" width="48" height="48" alt="nachx639" title="nachx639"/></a> <a href="https://github.com/pvoo"><img src="https://avatars.githubusercontent.com/u/20116814?v=4&s=48" width="48" height="48" alt="pvoo" title="pvoo"/></a> <a href="https://github.com/sreekaransrinath"><img src="https://avatars.githubusercontent.com/u/50989977?v=4&s=48" width="48" height="48" alt="sreekaransrinath" title="sreekaransrinath"/></a> <a href="https://github.com/gupsammy"><img src="https://avatars.githubusercontent.com/u/20296019?v=4&s=48" width="48" height="48" alt="gupsammy" title="gupsammy"/></a> <a href="https://github.com/cristip73"><img src="https://avatars.githubusercontent.com/u/24499421?v=4&s=48" width="48" height="48" alt="cristip73" title="cristip73"/></a>
|
||||||
<a href="https://github.com/peschee"><img src="https://avatars.githubusercontent.com/u/63866?v=4&s=48" width="48" height="48" alt="peschee" title="peschee"/></a> <a href="https://github.com/rafaelreis-r"><img src="https://avatars.githubusercontent.com/u/57492577?v=4&s=48" width="48" height="48" alt="rafaelreis-r" title="rafaelreis-r"/></a> <a href="https://github.com/thewilloftheshadow"><img src="https://avatars.githubusercontent.com/u/35580099?v=4&s=48" width="48" height="48" alt="thewilloftheshadow" title="thewilloftheshadow"/></a> <a href="https://github.com/ratulsarna"><img src="https://avatars.githubusercontent.com/u/105903728?v=4&s=48" width="48" height="48" alt="ratulsarna" title="ratulsarna"/></a> <a href="https://github.com/lutr0"><img src="https://avatars.githubusercontent.com/u/76906369?v=4&s=48" width="48" height="48" alt="lutr0" title="lutr0"/></a> <a href="https://github.com/danielz1z"><img src="https://avatars.githubusercontent.com/u/235270390?v=4&s=48" width="48" height="48" alt="danielz1z" title="danielz1z"/></a> <a href="https://github.com/emanuelst"><img src="https://avatars.githubusercontent.com/u/9994339?v=4&s=48" width="48" height="48" alt="emanuelst" title="emanuelst"/></a> <a href="https://github.com/KristijanJovanovski"><img src="https://avatars.githubusercontent.com/u/8942284?v=4&s=48" width="48" height="48" alt="KristijanJovanovski" title="KristijanJovanovski"/></a> <a href="https://github.com/CashWilliams"><img src="https://avatars.githubusercontent.com/u/613573?v=4&s=48" width="48" height="48" alt="CashWilliams" title="CashWilliams"/></a> <a href="https://github.com/rdev"><img src="https://avatars.githubusercontent.com/u/8418866?v=4&s=48" width="48" height="48" alt="rdev" title="rdev"/></a>
|
<a href="https://github.com/stefangalescu"><img src="https://avatars.githubusercontent.com/u/52995748?v=4&s=48" width="48" height="48" alt="stefangalescu" title="stefangalescu"/></a> <a href="https://github.com/nachoiacovino"><img src="https://avatars.githubusercontent.com/u/50103937?v=4&s=48" width="48" height="48" alt="nachoiacovino" title="nachoiacovino"/></a> <a href="https://github.com/vsabavat"><img src="https://avatars.githubusercontent.com/u/50385532?v=4&s=48" width="48" height="48" alt="Vasanth Rao Naik Sabavat" title="Vasanth Rao Naik Sabavat"/></a> <a href="https://github.com/petter-b"><img src="https://avatars.githubusercontent.com/u/62076402?v=4&s=48" width="48" height="48" alt="petter-b" title="petter-b"/></a> <a href="https://github.com/cpojer"><img src="https://avatars.githubusercontent.com/u/13352?v=4&s=48" width="48" height="48" alt="cpojer" title="cpojer"/></a> <a href="https://github.com/scald"><img src="https://avatars.githubusercontent.com/u/1215913?v=4&s=48" width="48" height="48" alt="scald" title="scald"/></a> <a href="https://github.com/gumadeiras"><img src="https://avatars.githubusercontent.com/u/5599352?v=4&s=48" width="48" height="48" alt="gumadeiras" title="gumadeiras"/></a> <a href="https://github.com/andranik-sahakyan"><img src="https://avatars.githubusercontent.com/u/8908029?v=4&s=48" width="48" height="48" alt="andranik-sahakyan" title="andranik-sahakyan"/></a> <a href="https://github.com/davidguttman"><img src="https://avatars.githubusercontent.com/u/431696?v=4&s=48" width="48" height="48" alt="davidguttman" title="davidguttman"/></a> <a href="https://github.com/sleontenko"><img src="https://avatars.githubusercontent.com/u/7135949?v=4&s=48" width="48" height="48" alt="sleontenko" title="sleontenko"/></a>
|
||||||
<a href="https://github.com/osolmaz"><img src="https://avatars.githubusercontent.com/u/2453968?v=4&s=48" width="48" height="48" alt="osolmaz" title="osolmaz"/></a> <a href="https://github.com/joshrad-dev"><img src="https://avatars.githubusercontent.com/u/62785552?v=4&s=48" width="48" height="48" alt="joshrad-dev" title="joshrad-dev"/></a> <a href="https://github.com/kiranjd"><img src="https://avatars.githubusercontent.com/u/25822851?v=4&s=48" width="48" height="48" alt="kiranjd" title="kiranjd"/></a> <a href="https://github.com/adityashaw2"><img src="https://avatars.githubusercontent.com/u/41204444?v=4&s=48" width="48" height="48" alt="adityashaw2" title="adityashaw2"/></a> <a href="https://github.com/search?q=sheeek"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="sheeek" title="sheeek"/></a> <a href="https://github.com/artuskg"><img src="https://avatars.githubusercontent.com/u/11966157?v=4&s=48" width="48" height="48" alt="artuskg" title="artuskg"/></a> <a href="https://github.com/onutc"><img src="https://avatars.githubusercontent.com/u/152018508?v=4&s=48" width="48" height="48" alt="onutc" title="onutc"/></a> <a href="https://github.com/pauloportella"><img src="https://avatars.githubusercontent.com/u/22947229?v=4&s=48" width="48" height="48" alt="pauloportella" title="pauloportella"/></a> <a href="https://github.com/tyler6204"><img src="https://avatars.githubusercontent.com/u/64381258?v=4&s=48" width="48" height="48" alt="tyler6204" title="tyler6204"/></a> <a href="https://github.com/neooriginal"><img src="https://avatars.githubusercontent.com/u/54811660?v=4&s=48" width="48" height="48" alt="neooriginal" title="neooriginal"/></a>
|
<a href="https://github.com/denysvitali"><img src="https://avatars.githubusercontent.com/u/4939519?v=4&s=48" width="48" height="48" alt="denysvitali" title="denysvitali"/></a> <a href="https://github.com/thewilloftheshadow"><img src="https://avatars.githubusercontent.com/u/35580099?v=4&s=48" width="48" height="48" alt="thewilloftheshadow" title="thewilloftheshadow"/></a> <a href="https://github.com/shakkernerd"><img src="https://avatars.githubusercontent.com/u/165377636?v=4&s=48" width="48" height="48" alt="shakkernerd" title="shakkernerd"/></a> <a href="https://github.com/sircrumpet"><img src="https://avatars.githubusercontent.com/u/4436535?v=4&s=48" width="48" height="48" alt="sircrumpet" title="sircrumpet"/></a> <a href="https://github.com/peschee"><img src="https://avatars.githubusercontent.com/u/63866?v=4&s=48" width="48" height="48" alt="peschee" title="peschee"/></a> <a href="https://github.com/rafaelreis-r"><img src="https://avatars.githubusercontent.com/u/57492577?v=4&s=48" width="48" height="48" alt="rafaelreis-r" title="rafaelreis-r"/></a> <a href="https://github.com/ratulsarna"><img src="https://avatars.githubusercontent.com/u/105903728?v=4&s=48" width="48" height="48" alt="ratulsarna" title="ratulsarna"/></a> <a href="https://github.com/lutr0"><img src="https://avatars.githubusercontent.com/u/76906369?v=4&s=48" width="48" height="48" alt="lutr0" title="lutr0"/></a> <a href="https://github.com/danielz1z"><img src="https://avatars.githubusercontent.com/u/235270390?v=4&s=48" width="48" height="48" alt="danielz1z" title="danielz1z"/></a> <a href="https://github.com/emanuelst"><img src="https://avatars.githubusercontent.com/u/9994339?v=4&s=48" width="48" height="48" alt="emanuelst" title="emanuelst"/></a>
|
||||||
<a href="https://github.com/ManuelHettich"><img src="https://avatars.githubusercontent.com/u/17690367?v=4&s=48" width="48" height="48" alt="manuelhettich" title="manuelhettich"/></a> <a href="https://github.com/minghinmatthewlam"><img src="https://avatars.githubusercontent.com/u/14224566?v=4&s=48" width="48" height="48" alt="minghinmatthewlam" title="minghinmatthewlam"/></a> <a href="https://github.com/myfunc"><img src="https://avatars.githubusercontent.com/u/19294627?v=4&s=48" width="48" height="48" alt="myfunc" title="myfunc"/></a> <a href="https://github.com/travisirby"><img src="https://avatars.githubusercontent.com/u/5958376?v=4&s=48" width="48" height="48" alt="travisirby" title="travisirby"/></a> <a href="https://github.com/buddyh"><img src="https://avatars.githubusercontent.com/u/31752869?v=4&s=48" width="48" height="48" alt="buddyh" title="buddyh"/></a> <a href="https://github.com/connorshea"><img src="https://avatars.githubusercontent.com/u/2977353?v=4&s=48" width="48" height="48" alt="connorshea" title="connorshea"/></a> <a href="https://github.com/mcinteerj"><img src="https://avatars.githubusercontent.com/u/3613653?v=4&s=48" width="48" height="48" alt="mcinteerj" title="mcinteerj"/></a> <a href="https://github.com/apps/dependabot"><img src="https://avatars.githubusercontent.com/in/29110?v=4&s=48" width="48" height="48" alt="dependabot[bot]" title="dependabot[bot]"/></a> <a href="https://github.com/John-Rood"><img src="https://avatars.githubusercontent.com/u/62669593?v=4&s=48" width="48" height="48" alt="John-Rood" title="John-Rood"/></a> <a href="https://github.com/timkrase"><img src="https://avatars.githubusercontent.com/u/38947626?v=4&s=48" width="48" height="48" alt="timkrase" title="timkrase"/></a>
|
<a href="https://github.com/KristijanJovanovski"><img src="https://avatars.githubusercontent.com/u/8942284?v=4&s=48" width="48" height="48" alt="KristijanJovanovski" title="KristijanJovanovski"/></a> <a href="https://github.com/rdev"><img src="https://avatars.githubusercontent.com/u/8418866?v=4&s=48" width="48" height="48" alt="rdev" title="rdev"/></a> <a href="https://github.com/rhuanssauro"><img src="https://avatars.githubusercontent.com/u/164682191?v=4&s=48" width="48" height="48" alt="rhuanssauro" title="rhuanssauro"/></a> <a href="https://github.com/joshrad-dev"><img src="https://avatars.githubusercontent.com/u/62785552?v=4&s=48" width="48" height="48" alt="joshrad-dev" title="joshrad-dev"/></a> <a href="https://github.com/kiranjd"><img src="https://avatars.githubusercontent.com/u/25822851?v=4&s=48" width="48" height="48" alt="kiranjd" title="kiranjd"/></a> <a href="https://github.com/osolmaz"><img src="https://avatars.githubusercontent.com/u/2453968?v=4&s=48" width="48" height="48" alt="osolmaz" title="osolmaz"/></a> <a href="https://github.com/adityashaw2"><img src="https://avatars.githubusercontent.com/u/41204444?v=4&s=48" width="48" height="48" alt="adityashaw2" title="adityashaw2"/></a> <a href="https://github.com/CashWilliams"><img src="https://avatars.githubusercontent.com/u/613573?v=4&s=48" width="48" height="48" alt="CashWilliams" title="CashWilliams"/></a> <a href="https://github.com/search?q=sheeek"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="sheeek" title="sheeek"/></a> <a href="https://github.com/artuskg"><img src="https://avatars.githubusercontent.com/u/11966157?v=4&s=48" width="48" height="48" alt="artuskg" title="artuskg"/></a>
|
||||||
<a href="https://github.com/gerardward2007"><img src="https://avatars.githubusercontent.com/u/3002155?v=4&s=48" width="48" height="48" alt="gerardward2007" title="gerardward2007"/></a> <a href="https://github.com/obviyus"><img src="https://avatars.githubusercontent.com/u/22031114?v=4&s=48" width="48" height="48" alt="obviyus" title="obviyus"/></a> <a href="https://github.com/tosh-hamburg"><img src="https://avatars.githubusercontent.com/u/58424326?v=4&s=48" width="48" height="48" alt="tosh-hamburg" title="tosh-hamburg"/></a> <a href="https://github.com/azade-c"><img src="https://avatars.githubusercontent.com/u/252790079?v=4&s=48" width="48" height="48" alt="azade-c" title="azade-c"/></a> <a href="https://github.com/roshanasingh4"><img src="https://avatars.githubusercontent.com/u/88576930?v=4&s=48" width="48" height="48" alt="roshanasingh4" title="roshanasingh4"/></a> <a href="https://github.com/bjesuiter"><img src="https://avatars.githubusercontent.com/u/2365676?v=4&s=48" width="48" height="48" alt="bjesuiter" title="bjesuiter"/></a> <a href="https://github.com/cheeeee"><img src="https://avatars.githubusercontent.com/u/21245729?v=4&s=48" width="48" height="48" alt="cheeeee" title="cheeeee"/></a> <a href="https://github.com/j1philli"><img src="https://avatars.githubusercontent.com/u/3744255?v=4&s=48" width="48" height="48" alt="Josh Phillips" title="Josh Phillips"/></a> <a href="https://github.com/pookNast"><img src="https://avatars.githubusercontent.com/u/14242552?v=4&s=48" width="48" height="48" alt="pookNast" title="pookNast"/></a> <a href="https://github.com/Whoaa512"><img src="https://avatars.githubusercontent.com/u/1581943?v=4&s=48" width="48" height="48" alt="Whoaa512" title="Whoaa512"/></a>
|
<a href="https://github.com/Takhoffman"><img src="https://avatars.githubusercontent.com/u/781889?v=4&s=48" width="48" height="48" alt="Takhoffman" title="Takhoffman"/></a> <a href="https://github.com/onutc"><img src="https://avatars.githubusercontent.com/u/152018508?v=4&s=48" width="48" height="48" alt="onutc" title="onutc"/></a> <a href="https://github.com/pauloportella"><img src="https://avatars.githubusercontent.com/u/22947229?v=4&s=48" width="48" height="48" alt="pauloportella" title="pauloportella"/></a> <a href="https://github.com/neooriginal"><img src="https://avatars.githubusercontent.com/u/54811660?v=4&s=48" width="48" height="48" alt="neooriginal" title="neooriginal"/></a> <a href="https://github.com/ManuelHettich"><img src="https://avatars.githubusercontent.com/u/17690367?v=4&s=48" width="48" height="48" alt="manuelhettich" title="manuelhettich"/></a> <a href="https://github.com/minghinmatthewlam"><img src="https://avatars.githubusercontent.com/u/14224566?v=4&s=48" width="48" height="48" alt="minghinmatthewlam" title="minghinmatthewlam"/></a> <a href="https://github.com/myfunc"><img src="https://avatars.githubusercontent.com/u/19294627?v=4&s=48" width="48" height="48" alt="myfunc" title="myfunc"/></a> <a href="https://github.com/travisirby"><img src="https://avatars.githubusercontent.com/u/5958376?v=4&s=48" width="48" height="48" alt="travisirby" title="travisirby"/></a> <a href="https://github.com/buddyh"><img src="https://avatars.githubusercontent.com/u/31752869?v=4&s=48" width="48" height="48" alt="buddyh" title="buddyh"/></a> <a href="https://github.com/connorshea"><img src="https://avatars.githubusercontent.com/u/2977353?v=4&s=48" width="48" height="48" alt="connorshea" title="connorshea"/></a>
|
||||||
<a href="https://github.com/YuriNachos"><img src="https://avatars.githubusercontent.com/u/19365375?v=4&s=48" width="48" height="48" alt="YuriNachos" title="YuriNachos"/></a> <a href="https://github.com/chriseidhof"><img src="https://avatars.githubusercontent.com/u/5382?v=4&s=48" width="48" height="48" alt="chriseidhof" title="chriseidhof"/></a> <a href="https://github.com/dlauer"><img src="https://avatars.githubusercontent.com/u/757041?v=4&s=48" width="48" height="48" alt="dlauer" title="dlauer"/></a> <a href="https://github.com/robbyczgw-cla"><img src="https://avatars.githubusercontent.com/u/239660374?v=4&s=48" width="48" height="48" alt="robbyczgw-cla" title="robbyczgw-cla"/></a> <a href="https://github.com/ysqander"><img src="https://avatars.githubusercontent.com/u/80843820?v=4&s=48" width="48" height="48" alt="ysqander" title="ysqander"/></a> <a href="https://github.com/aj47"><img src="https://avatars.githubusercontent.com/u/8023513?v=4&s=48" width="48" height="48" alt="aj47" title="aj47"/></a> <a href="https://github.com/superman32432432"><img src="https://avatars.githubusercontent.com/u/7228420?v=4&s=48" width="48" height="48" alt="superman32432432" title="superman32432432"/></a> <a href="https://github.com/Takhoffman"><img src="https://avatars.githubusercontent.com/u/781889?v=4&s=48" width="48" height="48" alt="Takhoffman" title="Takhoffman"/></a> <a href="https://github.com/search?q=Yurii%20Chukhlib"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Yurii Chukhlib" title="Yurii Chukhlib"/></a> <a href="https://github.com/grp06"><img src="https://avatars.githubusercontent.com/u/1573959?v=4&s=48" width="48" height="48" alt="grp06" title="grp06"/></a>
|
<a href="https://github.com/kyleok"><img src="https://avatars.githubusercontent.com/u/58307870?v=4&s=48" width="48" height="48" alt="kyleok" title="kyleok"/></a> <a href="https://github.com/mcinteerj"><img src="https://avatars.githubusercontent.com/u/3613653?v=4&s=48" width="48" height="48" alt="mcinteerj" title="mcinteerj"/></a> <a href="https://github.com/apps/dependabot"><img src="https://avatars.githubusercontent.com/in/29110?v=4&s=48" width="48" height="48" alt="dependabot[bot]" title="dependabot[bot]"/></a> <a href="https://github.com/John-Rood"><img src="https://avatars.githubusercontent.com/u/62669593?v=4&s=48" width="48" height="48" alt="John-Rood" title="John-Rood"/></a> <a href="https://github.com/timkrase"><img src="https://avatars.githubusercontent.com/u/38947626?v=4&s=48" width="48" height="48" alt="timkrase" title="timkrase"/></a> <a href="https://github.com/uos-status"><img src="https://avatars.githubusercontent.com/u/255712580?v=4&s=48" width="48" height="48" alt="uos-status" title="uos-status"/></a> <a href="https://github.com/gerardward2007"><img src="https://avatars.githubusercontent.com/u/3002155?v=4&s=48" width="48" height="48" alt="gerardward2007" title="gerardward2007"/></a> <a href="https://github.com/obviyus"><img src="https://avatars.githubusercontent.com/u/22031114?v=4&s=48" width="48" height="48" alt="obviyus" title="obviyus"/></a> <a href="https://github.com/roshanasingh4"><img src="https://avatars.githubusercontent.com/u/88576930?v=4&s=48" width="48" height="48" alt="roshanasingh4" title="roshanasingh4"/></a> <a href="https://github.com/tosh-hamburg"><img src="https://avatars.githubusercontent.com/u/58424326?v=4&s=48" width="48" height="48" alt="tosh-hamburg" title="tosh-hamburg"/></a>
|
||||||
<a href="https://github.com/antons"><img src="https://avatars.githubusercontent.com/u/129705?v=4&s=48" width="48" height="48" alt="antons" title="antons"/></a> <a href="https://github.com/austinm911"><img src="https://avatars.githubusercontent.com/u/31991302?v=4&s=48" width="48" height="48" alt="austinm911" title="austinm911"/></a> <a href="https://github.com/apps/blacksmith-sh"><img src="https://avatars.githubusercontent.com/in/807020?v=4&s=48" width="48" height="48" alt="blacksmith-sh[bot]" title="blacksmith-sh[bot]"/></a> <a href="https://github.com/damoahdominic"><img src="https://avatars.githubusercontent.com/u/4623434?v=4&s=48" width="48" height="48" alt="damoahdominic" title="damoahdominic"/></a> <a href="https://github.com/dan-dr"><img src="https://avatars.githubusercontent.com/u/6669808?v=4&s=48" width="48" height="48" alt="dan-dr" title="dan-dr"/></a> <a href="https://github.com/HeimdallStrategy"><img src="https://avatars.githubusercontent.com/u/223014405?v=4&s=48" width="48" height="48" alt="HeimdallStrategy" title="HeimdallStrategy"/></a> <a href="https://github.com/imfing"><img src="https://avatars.githubusercontent.com/u/5097752?v=4&s=48" width="48" height="48" alt="imfing" title="imfing"/></a> <a href="https://github.com/jalehman"><img src="https://avatars.githubusercontent.com/u/550978?v=4&s=48" width="48" height="48" alt="jalehman" title="jalehman"/></a> <a href="https://github.com/jarvis-medmatic"><img src="https://avatars.githubusercontent.com/u/252428873?v=4&s=48" width="48" height="48" alt="jarvis-medmatic" title="jarvis-medmatic"/></a> <a href="https://github.com/kkarimi"><img src="https://avatars.githubusercontent.com/u/875218?v=4&s=48" width="48" height="48" alt="kkarimi" title="kkarimi"/></a>
|
<a href="https://github.com/azade-c"><img src="https://avatars.githubusercontent.com/u/252790079?v=4&s=48" width="48" height="48" alt="azade-c" title="azade-c"/></a> <a href="https://github.com/JonUleis"><img src="https://avatars.githubusercontent.com/u/7644941?v=4&s=48" width="48" height="48" alt="JonUleis" title="JonUleis"/></a> <a href="https://github.com/bjesuiter"><img src="https://avatars.githubusercontent.com/u/2365676?v=4&s=48" width="48" height="48" alt="bjesuiter" title="bjesuiter"/></a> <a href="https://github.com/cheeeee"><img src="https://avatars.githubusercontent.com/u/21245729?v=4&s=48" width="48" height="48" alt="cheeeee" title="cheeeee"/></a> <a href="https://github.com/j1philli"><img src="https://avatars.githubusercontent.com/u/3744255?v=4&s=48" width="48" height="48" alt="Josh Phillips" title="Josh Phillips"/></a> <a href="https://github.com/YuriNachos"><img src="https://avatars.githubusercontent.com/u/19365375?v=4&s=48" width="48" height="48" alt="YuriNachos" title="YuriNachos"/></a> <a href="https://github.com/robbyczgw-cla"><img src="https://avatars.githubusercontent.com/u/239660374?v=4&s=48" width="48" height="48" alt="robbyczgw-cla" title="robbyczgw-cla"/></a> <a href="https://github.com/dlauer"><img src="https://avatars.githubusercontent.com/u/757041?v=4&s=48" width="48" height="48" alt="dlauer" title="dlauer"/></a> <a href="https://github.com/pookNast"><img src="https://avatars.githubusercontent.com/u/14242552?v=4&s=48" width="48" height="48" alt="pookNast" title="pookNast"/></a> <a href="https://github.com/Whoaa512"><img src="https://avatars.githubusercontent.com/u/1581943?v=4&s=48" width="48" height="48" alt="Whoaa512" title="Whoaa512"/></a>
|
||||||
<a href="https://github.com/mahmoudashraf93"><img src="https://avatars.githubusercontent.com/u/9130129?v=4&s=48" width="48" height="48" alt="mahmoudashraf93" title="mahmoudashraf93"/></a> <a href="https://github.com/ngutman"><img src="https://avatars.githubusercontent.com/u/1540134?v=4&s=48" width="48" height="48" alt="ngutman" title="ngutman"/></a> <a href="https://github.com/petter-b"><img src="https://avatars.githubusercontent.com/u/62076402?v=4&s=48" width="48" height="48" alt="petter-b" title="petter-b"/></a> <a href="https://github.com/pkrmf"><img src="https://avatars.githubusercontent.com/u/1714267?v=4&s=48" width="48" height="48" alt="pkrmf" title="pkrmf"/></a> <a href="https://github.com/RandyVentures"><img src="https://avatars.githubusercontent.com/u/149904821?v=4&s=48" width="48" height="48" alt="RandyVentures" title="RandyVentures"/></a> <a href="https://github.com/search?q=Ryan%20Lisse"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ryan Lisse" title="Ryan Lisse"/></a> <a href="https://github.com/dougvk"><img src="https://avatars.githubusercontent.com/u/401660?v=4&s=48" width="48" height="48" alt="dougvk" title="dougvk"/></a> <a href="https://github.com/erikpr1994"><img src="https://avatars.githubusercontent.com/u/6299331?v=4&s=48" width="48" height="48" alt="erikpr1994" title="erikpr1994"/></a> <a href="https://github.com/search?q=Ghost"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ghost" title="Ghost"/></a> <a href="https://github.com/jonasjancarik"><img src="https://avatars.githubusercontent.com/u/2459191?v=4&s=48" width="48" height="48" alt="jonasjancarik" title="jonasjancarik"/></a>
|
<a href="https://github.com/chriseidhof"><img src="https://avatars.githubusercontent.com/u/5382?v=4&s=48" width="48" height="48" alt="chriseidhof" title="chriseidhof"/></a> <a href="https://github.com/ngutman"><img src="https://avatars.githubusercontent.com/u/1540134?v=4&s=48" width="48" height="48" alt="ngutman" title="ngutman"/></a> <a href="https://github.com/ysqander"><img src="https://avatars.githubusercontent.com/u/80843820?v=4&s=48" width="48" height="48" alt="ysqander" title="ysqander"/></a> <a href="https://github.com/aj47"><img src="https://avatars.githubusercontent.com/u/8023513?v=4&s=48" width="48" height="48" alt="aj47" title="aj47"/></a> <a href="https://github.com/superman32432432"><img src="https://avatars.githubusercontent.com/u/7228420?v=4&s=48" width="48" height="48" alt="superman32432432" title="superman32432432"/></a> <a href="https://github.com/search?q=Yurii%20Chukhlib"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Yurii Chukhlib" title="Yurii Chukhlib"/></a> <a href="https://github.com/grp06"><img src="https://avatars.githubusercontent.com/u/1573959?v=4&s=48" width="48" height="48" alt="grp06" title="grp06"/></a> <a href="https://github.com/antons"><img src="https://avatars.githubusercontent.com/u/129705?v=4&s=48" width="48" height="48" alt="antons" title="antons"/></a> <a href="https://github.com/austinm911"><img src="https://avatars.githubusercontent.com/u/31991302?v=4&s=48" width="48" height="48" alt="austinm911" title="austinm911"/></a> <a href="https://github.com/apps/blacksmith-sh"><img src="https://avatars.githubusercontent.com/in/807020?v=4&s=48" width="48" height="48" alt="blacksmith-sh[bot]" title="blacksmith-sh[bot]"/></a>
|
||||||
<a href="https://github.com/search?q=Keith%20the%20Silly%20Goose"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Keith the Silly Goose" title="Keith the Silly Goose"/></a> <a href="https://github.com/search?q=L36%20Server"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="L36 Server" title="L36 Server"/></a> <a href="https://github.com/search?q=Marc"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Marc" title="Marc"/></a> <a href="https://github.com/mitschabaude-bot"><img src="https://avatars.githubusercontent.com/u/247582884?v=4&s=48" width="48" height="48" alt="mitschabaude-bot" title="mitschabaude-bot"/></a> <a href="https://github.com/mkbehr"><img src="https://avatars.githubusercontent.com/u/1285?v=4&s=48" width="48" height="48" alt="mkbehr" title="mkbehr"/></a> <a href="https://github.com/neist"><img src="https://avatars.githubusercontent.com/u/1029724?v=4&s=48" width="48" height="48" alt="neist" title="neist"/></a> <a href="https://github.com/sibbl"><img src="https://avatars.githubusercontent.com/u/866535?v=4&s=48" width="48" height="48" alt="sibbl" title="sibbl"/></a> <a href="https://github.com/chrisrodz"><img src="https://avatars.githubusercontent.com/u/2967620?v=4&s=48" width="48" height="48" alt="chrisrodz" title="chrisrodz"/></a> <a href="https://github.com/czekaj"><img src="https://avatars.githubusercontent.com/u/1464539?v=4&s=48" width="48" height="48" alt="czekaj" title="czekaj"/></a> <a href="https://github.com/search?q=Friederike%20Seiler"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Friederike Seiler" title="Friederike Seiler"/></a>
|
<a href="https://github.com/damoahdominic"><img src="https://avatars.githubusercontent.com/u/4623434?v=4&s=48" width="48" height="48" alt="damoahdominic" title="damoahdominic"/></a> <a href="https://github.com/dan-dr"><img src="https://avatars.githubusercontent.com/u/6669808?v=4&s=48" width="48" height="48" alt="dan-dr" title="dan-dr"/></a> <a href="https://github.com/HeimdallStrategy"><img src="https://avatars.githubusercontent.com/u/223014405?v=4&s=48" width="48" height="48" alt="HeimdallStrategy" title="HeimdallStrategy"/></a> <a href="https://github.com/imfing"><img src="https://avatars.githubusercontent.com/u/5097752?v=4&s=48" width="48" height="48" alt="imfing" title="imfing"/></a> <a href="https://github.com/jalehman"><img src="https://avatars.githubusercontent.com/u/550978?v=4&s=48" width="48" height="48" alt="jalehman" title="jalehman"/></a> <a href="https://github.com/jarvis-medmatic"><img src="https://avatars.githubusercontent.com/u/252428873?v=4&s=48" width="48" height="48" alt="jarvis-medmatic" title="jarvis-medmatic"/></a> <a href="https://github.com/kkarimi"><img src="https://avatars.githubusercontent.com/u/875218?v=4&s=48" width="48" height="48" alt="kkarimi" title="kkarimi"/></a> <a href="https://github.com/mahmoudashraf93"><img src="https://avatars.githubusercontent.com/u/9130129?v=4&s=48" width="48" height="48" alt="mahmoudashraf93" title="mahmoudashraf93"/></a> <a href="https://github.com/pkrmf"><img src="https://avatars.githubusercontent.com/u/1714267?v=4&s=48" width="48" height="48" alt="pkrmf" title="pkrmf"/></a> <a href="https://github.com/RandyVentures"><img src="https://avatars.githubusercontent.com/u/149904821?v=4&s=48" width="48" height="48" alt="RandyVentures" title="RandyVentures"/></a>
|
||||||
<a href="https://github.com/gabriel-trigo"><img src="https://avatars.githubusercontent.com/u/38991125?v=4&s=48" width="48" height="48" alt="gabriel-trigo" title="gabriel-trigo"/></a> <a href="https://github.com/Iamadig"><img src="https://avatars.githubusercontent.com/u/102129234?v=4&s=48" width="48" height="48" alt="iamadig" title="iamadig"/></a> <a href="https://github.com/jdrhyne"><img src="https://avatars.githubusercontent.com/u/7828464?v=4&s=48" width="48" height="48" alt="Jonathan D. Rhyne (DJ-D)" title="Jonathan D. Rhyne (DJ-D)"/></a> <a href="https://github.com/search?q=Kit"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kit" title="Kit"/></a> <a href="https://github.com/koala73"><img src="https://avatars.githubusercontent.com/u/996596?v=4&s=48" width="48" height="48" alt="koala73" title="koala73"/></a> <a href="https://github.com/manmal"><img src="https://avatars.githubusercontent.com/u/142797?v=4&s=48" width="48" height="48" alt="manmal" title="manmal"/></a> <a href="https://github.com/ogulcancelik"><img src="https://avatars.githubusercontent.com/u/7064011?v=4&s=48" width="48" height="48" alt="ogulcancelik" title="ogulcancelik"/></a> <a href="https://github.com/pasogott"><img src="https://avatars.githubusercontent.com/u/23458152?v=4&s=48" width="48" height="48" alt="pasogott" title="pasogott"/></a> <a href="https://github.com/petradonka"><img src="https://avatars.githubusercontent.com/u/7353770?v=4&s=48" width="48" height="48" alt="petradonka" title="petradonka"/></a> <a href="https://github.com/rubyrunsstuff"><img src="https://avatars.githubusercontent.com/u/246602379?v=4&s=48" width="48" height="48" alt="rubyrunsstuff" title="rubyrunsstuff"/></a>
|
<a href="https://github.com/search?q=Ryan%20Lisse"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ryan Lisse" title="Ryan Lisse"/></a> <a href="https://github.com/dougvk"><img src="https://avatars.githubusercontent.com/u/401660?v=4&s=48" width="48" height="48" alt="dougvk" title="dougvk"/></a> <a href="https://github.com/erikpr1994"><img src="https://avatars.githubusercontent.com/u/6299331?v=4&s=48" width="48" height="48" alt="erikpr1994" title="erikpr1994"/></a> <a href="https://github.com/search?q=Ghost"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ghost" title="Ghost"/></a> <a href="https://github.com/jonasjancarik"><img src="https://avatars.githubusercontent.com/u/2459191?v=4&s=48" width="48" height="48" alt="jonasjancarik" title="jonasjancarik"/></a> <a href="https://github.com/search?q=Keith%20the%20Silly%20Goose"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Keith the Silly Goose" title="Keith the Silly Goose"/></a> <a href="https://github.com/search?q=L36%20Server"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="L36 Server" title="L36 Server"/></a> <a href="https://github.com/search?q=Marc"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Marc" title="Marc"/></a> <a href="https://github.com/mitschabaude-bot"><img src="https://avatars.githubusercontent.com/u/247582884?v=4&s=48" width="48" height="48" alt="mitschabaude-bot" title="mitschabaude-bot"/></a> <a href="https://github.com/mkbehr"><img src="https://avatars.githubusercontent.com/u/1285?v=4&s=48" width="48" height="48" alt="mkbehr" title="mkbehr"/></a>
|
||||||
<a href="https://github.com/siddhantjain"><img src="https://avatars.githubusercontent.com/u/4835232?v=4&s=48" width="48" height="48" alt="siddhantjain" title="siddhantjain"/></a> <a href="https://github.com/suminhthanh"><img src="https://avatars.githubusercontent.com/u/2907636?v=4&s=48" width="48" height="48" alt="suminhthanh" title="suminhthanh"/></a> <a href="https://github.com/svkozak"><img src="https://avatars.githubusercontent.com/u/31941359?v=4&s=48" width="48" height="48" alt="svkozak" title="svkozak"/></a> <a href="https://github.com/VACInc"><img src="https://avatars.githubusercontent.com/u/3279061?v=4&s=48" width="48" height="48" alt="VACInc" title="VACInc"/></a> <a href="https://github.com/wes-davis"><img src="https://avatars.githubusercontent.com/u/16506720?v=4&s=48" width="48" height="48" alt="wes-davis" title="wes-davis"/></a> <a href="https://github.com/zats"><img src="https://avatars.githubusercontent.com/u/2688806?v=4&s=48" width="48" height="48" alt="zats" title="zats"/></a> <a href="https://github.com/24601"><img src="https://avatars.githubusercontent.com/u/1157207?v=4&s=48" width="48" height="48" alt="24601" title="24601"/></a> <a href="https://github.com/adam91holt"><img src="https://avatars.githubusercontent.com/u/9592417?v=4&s=48" width="48" height="48" alt="adam91holt" title="adam91holt"/></a> <a href="https://github.com/ameno-"><img src="https://avatars.githubusercontent.com/u/2416135?v=4&s=48" width="48" height="48" alt="ameno-" title="ameno-"/></a> <a href="https://github.com/search?q=Chris%20Taylor"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Chris Taylor" title="Chris Taylor"/></a>
|
<a href="https://github.com/neist"><img src="https://avatars.githubusercontent.com/u/1029724?v=4&s=48" width="48" height="48" alt="neist" title="neist"/></a> <a href="https://github.com/sibbl"><img src="https://avatars.githubusercontent.com/u/866535?v=4&s=48" width="48" height="48" alt="sibbl" title="sibbl"/></a> <a href="https://github.com/chrisrodz"><img src="https://avatars.githubusercontent.com/u/2967620?v=4&s=48" width="48" height="48" alt="chrisrodz" title="chrisrodz"/></a> <a href="https://github.com/search?q=Friederike%20Seiler"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Friederike Seiler" title="Friederike Seiler"/></a> <a href="https://github.com/gabriel-trigo"><img src="https://avatars.githubusercontent.com/u/38991125?v=4&s=48" width="48" height="48" alt="gabriel-trigo" title="gabriel-trigo"/></a> <a href="https://github.com/Iamadig"><img src="https://avatars.githubusercontent.com/u/102129234?v=4&s=48" width="48" height="48" alt="iamadig" title="iamadig"/></a> <a href="https://github.com/jdrhyne"><img src="https://avatars.githubusercontent.com/u/7828464?v=4&s=48" width="48" height="48" alt="Jonathan D. Rhyne (DJ-D)" title="Jonathan D. Rhyne (DJ-D)"/></a> <a href="https://github.com/search?q=Kit"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kit" title="Kit"/></a> <a href="https://github.com/koala73"><img src="https://avatars.githubusercontent.com/u/996596?v=4&s=48" width="48" height="48" alt="koala73" title="koala73"/></a> <a href="https://github.com/manmal"><img src="https://avatars.githubusercontent.com/u/142797?v=4&s=48" width="48" height="48" alt="manmal" title="manmal"/></a>
|
||||||
<a href="https://github.com/djangonavarro220"><img src="https://avatars.githubusercontent.com/u/251162586?v=4&s=48" width="48" height="48" alt="Django Navarro" title="Django Navarro"/></a> <a href="https://github.com/evalexpr"><img src="https://avatars.githubusercontent.com/u/23485511?v=4&s=48" width="48" height="48" alt="evalexpr" title="evalexpr"/></a> <a href="https://github.com/henrino3"><img src="https://avatars.githubusercontent.com/u/4260288?v=4&s=48" width="48" height="48" alt="henrino3" title="henrino3"/></a> <a href="https://github.com/humanwritten"><img src="https://avatars.githubusercontent.com/u/206531610?v=4&s=48" width="48" height="48" alt="humanwritten" title="humanwritten"/></a> <a href="https://github.com/larlyssa"><img src="https://avatars.githubusercontent.com/u/13128869?v=4&s=48" width="48" height="48" alt="larlyssa" title="larlyssa"/></a> <a href="https://github.com/odysseus0"><img src="https://avatars.githubusercontent.com/u/8635094?v=4&s=48" width="48" height="48" alt="odysseus0" title="odysseus0"/></a> <a href="https://github.com/oswalpalash"><img src="https://avatars.githubusercontent.com/u/6431196?v=4&s=48" width="48" height="48" alt="oswalpalash" title="oswalpalash"/></a> <a href="https://github.com/pcty-nextgen-service-account"><img src="https://avatars.githubusercontent.com/u/112553441?v=4&s=48" width="48" height="48" alt="pcty-nextgen-service-account" title="pcty-nextgen-service-account"/></a> <a href="https://github.com/Syhids"><img src="https://avatars.githubusercontent.com/u/671202?v=4&s=48" width="48" height="48" alt="Syhids" title="Syhids"/></a> <a href="https://github.com/search?q=Aaron%20Konyer"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Aaron Konyer" title="Aaron Konyer"/></a>
|
<a href="https://github.com/ogulcancelik"><img src="https://avatars.githubusercontent.com/u/7064011?v=4&s=48" width="48" height="48" alt="ogulcancelik" title="ogulcancelik"/></a> <a href="https://github.com/pasogott"><img src="https://avatars.githubusercontent.com/u/23458152?v=4&s=48" width="48" height="48" alt="pasogott" title="pasogott"/></a> <a href="https://github.com/petradonka"><img src="https://avatars.githubusercontent.com/u/7353770?v=4&s=48" width="48" height="48" alt="petradonka" title="petradonka"/></a> <a href="https://github.com/rubyrunsstuff"><img src="https://avatars.githubusercontent.com/u/246602379?v=4&s=48" width="48" height="48" alt="rubyrunsstuff" title="rubyrunsstuff"/></a> <a href="https://github.com/siddhantjain"><img src="https://avatars.githubusercontent.com/u/4835232?v=4&s=48" width="48" height="48" alt="siddhantjain" title="siddhantjain"/></a> <a href="https://github.com/suminhthanh"><img src="https://avatars.githubusercontent.com/u/2907636?v=4&s=48" width="48" height="48" alt="suminhthanh" title="suminhthanh"/></a> <a href="https://github.com/svkozak"><img src="https://avatars.githubusercontent.com/u/31941359?v=4&s=48" width="48" height="48" alt="svkozak" title="svkozak"/></a> <a href="https://github.com/VACInc"><img src="https://avatars.githubusercontent.com/u/3279061?v=4&s=48" width="48" height="48" alt="VACInc" title="VACInc"/></a> <a href="https://github.com/wes-davis"><img src="https://avatars.githubusercontent.com/u/16506720?v=4&s=48" width="48" height="48" alt="wes-davis" title="wes-davis"/></a> <a href="https://github.com/zats"><img src="https://avatars.githubusercontent.com/u/2688806?v=4&s=48" width="48" height="48" alt="zats" title="zats"/></a>
|
||||||
<a href="https://github.com/aaronveklabs"><img src="https://avatars.githubusercontent.com/u/225997828?v=4&s=48" width="48" height="48" alt="aaronveklabs" title="aaronveklabs"/></a> <a href="https://github.com/andreabadesso"><img src="https://avatars.githubusercontent.com/u/3586068?v=4&s=48" width="48" height="48" alt="andreabadesso" title="andreabadesso"/></a> <a href="https://github.com/cash-echo-bot"><img src="https://avatars.githubusercontent.com/u/252747386?v=4&s=48" width="48" height="48" alt="cash-echo-bot" title="cash-echo-bot"/></a> <a href="https://github.com/search?q=Clawd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Clawd" title="Clawd"/></a> <a href="https://github.com/search?q=ClawdFx"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ClawdFx" title="ClawdFx"/></a> <a href="https://github.com/erik-agens"><img src="https://avatars.githubusercontent.com/u/80908960?v=4&s=48" width="48" height="48" alt="erik-agens" title="erik-agens"/></a> <a href="https://github.com/fcatuhe"><img src="https://avatars.githubusercontent.com/u/17382215?v=4&s=48" width="48" height="48" alt="fcatuhe" title="fcatuhe"/></a> <a href="https://github.com/ivanrvpereira"><img src="https://avatars.githubusercontent.com/u/183991?v=4&s=48" width="48" height="48" alt="ivanrvpereira" title="ivanrvpereira"/></a> <a href="https://github.com/jayhickey"><img src="https://avatars.githubusercontent.com/u/1676460?v=4&s=48" width="48" height="48" alt="jayhickey" title="jayhickey"/></a> <a href="https://github.com/jeffersonwarrior"><img src="https://avatars.githubusercontent.com/u/89030989?v=4&s=48" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a>
|
<a href="https://github.com/24601"><img src="https://avatars.githubusercontent.com/u/1157207?v=4&s=48" width="48" height="48" alt="24601" title="24601"/></a> <a href="https://github.com/adam91holt"><img src="https://avatars.githubusercontent.com/u/9592417?v=4&s=48" width="48" height="48" alt="adam91holt" title="adam91holt"/></a> <a href="https://github.com/ameno-"><img src="https://avatars.githubusercontent.com/u/2416135?v=4&s=48" width="48" height="48" alt="ameno-" title="ameno-"/></a> <a href="https://github.com/search?q=Chris%20Taylor"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Chris Taylor" title="Chris Taylor"/></a> <a href="https://github.com/djangonavarro220"><img src="https://avatars.githubusercontent.com/u/251162586?v=4&s=48" width="48" height="48" alt="Django Navarro" title="Django Navarro"/></a> <a href="https://github.com/evalexpr"><img src="https://avatars.githubusercontent.com/u/23485511?v=4&s=48" width="48" height="48" alt="evalexpr" title="evalexpr"/></a> <a href="https://github.com/henrino3"><img src="https://avatars.githubusercontent.com/u/4260288?v=4&s=48" width="48" height="48" alt="henrino3" title="henrino3"/></a> <a href="https://github.com/humanwritten"><img src="https://avatars.githubusercontent.com/u/206531610?v=4&s=48" width="48" height="48" alt="humanwritten" title="humanwritten"/></a> <a href="https://github.com/larlyssa"><img src="https://avatars.githubusercontent.com/u/13128869?v=4&s=48" width="48" height="48" alt="larlyssa" title="larlyssa"/></a> <a href="https://github.com/odysseus0"><img src="https://avatars.githubusercontent.com/u/8635094?v=4&s=48" width="48" height="48" alt="odysseus0" title="odysseus0"/></a>
|
||||||
<a href="https://github.com/search?q=jeffersonwarrior"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a> <a href="https://github.com/jverdi"><img src="https://avatars.githubusercontent.com/u/345050?v=4&s=48" width="48" height="48" alt="jverdi" title="jverdi"/></a> <a href="https://github.com/longmaba"><img src="https://avatars.githubusercontent.com/u/9361500?v=4&s=48" width="48" height="48" alt="longmaba" title="longmaba"/></a> <a href="https://github.com/mickahouan"><img src="https://avatars.githubusercontent.com/u/31423109?v=4&s=48" width="48" height="48" alt="mickahouan" title="mickahouan"/></a> <a href="https://github.com/mjrussell"><img src="https://avatars.githubusercontent.com/u/1641895?v=4&s=48" width="48" height="48" alt="mjrussell" title="mjrussell"/></a> <a href="https://github.com/p6l-richard"><img src="https://avatars.githubusercontent.com/u/18185649?v=4&s=48" width="48" height="48" alt="p6l-richard" title="p6l-richard"/></a> <a href="https://github.com/philipp-spiess"><img src="https://avatars.githubusercontent.com/u/458591?v=4&s=48" width="48" height="48" alt="philipp-spiess" title="philipp-spiess"/></a> <a href="https://github.com/robaxelsen"><img src="https://avatars.githubusercontent.com/u/13132899?v=4&s=48" width="48" height="48" alt="robaxelsen" title="robaxelsen"/></a> <a href="https://github.com/search?q=Sash%20Catanzarite"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Sash Catanzarite" title="Sash Catanzarite"/></a> <a href="https://github.com/T5-AndyML"><img src="https://avatars.githubusercontent.com/u/22801233?v=4&s=48" width="48" height="48" alt="T5-AndyML" title="T5-AndyML"/></a>
|
<a href="https://github.com/oswalpalash"><img src="https://avatars.githubusercontent.com/u/6431196?v=4&s=48" width="48" height="48" alt="oswalpalash" title="oswalpalash"/></a> <a href="https://github.com/pcty-nextgen-service-account"><img src="https://avatars.githubusercontent.com/u/112553441?v=4&s=48" width="48" height="48" alt="pcty-nextgen-service-account" title="pcty-nextgen-service-account"/></a> <a href="https://github.com/rmorse"><img src="https://avatars.githubusercontent.com/u/853547?v=4&s=48" width="48" height="48" alt="rmorse" title="rmorse"/></a> <a href="https://github.com/Syhids"><img src="https://avatars.githubusercontent.com/u/671202?v=4&s=48" width="48" height="48" alt="Syhids" title="Syhids"/></a> <a href="https://github.com/search?q=Aaron%20Konyer"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Aaron Konyer" title="Aaron Konyer"/></a> <a href="https://github.com/aaronveklabs"><img src="https://avatars.githubusercontent.com/u/225997828?v=4&s=48" width="48" height="48" alt="aaronveklabs" title="aaronveklabs"/></a> <a href="https://github.com/andreabadesso"><img src="https://avatars.githubusercontent.com/u/3586068?v=4&s=48" width="48" height="48" alt="andreabadesso" title="andreabadesso"/></a> <a href="https://github.com/search?q=Andrii"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Andrii" title="Andrii"/></a> <a href="https://github.com/cash-echo-bot"><img src="https://avatars.githubusercontent.com/u/252747386?v=4&s=48" width="48" height="48" alt="cash-echo-bot" title="cash-echo-bot"/></a> <a href="https://github.com/search?q=Clawd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Clawd" title="Clawd"/></a>
|
||||||
<a href="https://github.com/travisp"><img src="https://avatars.githubusercontent.com/u/165698?v=4&s=48" width="48" height="48" alt="travisp" title="travisp"/></a> <a href="https://github.com/search?q=VAC"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="VAC" title="VAC"/></a> <a href="https://github.com/search?q=william%20arzt"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="william arzt" title="william arzt"/></a> <a href="https://github.com/zknicker"><img src="https://avatars.githubusercontent.com/u/1164085?v=4&s=48" width="48" height="48" alt="zknicker" title="zknicker"/></a> <a href="https://github.com/search?q=alejandro%20maza"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="alejandro maza" title="alejandro maza"/></a> <a href="https://github.com/andrewting19"><img src="https://avatars.githubusercontent.com/u/10536704?v=4&s=48" width="48" height="48" alt="andrewting19" title="andrewting19"/></a> <a href="https://github.com/search?q=Andrii"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Andrii" title="Andrii"/></a> <a href="https://github.com/anpoirier"><img src="https://avatars.githubusercontent.com/u/1245729?v=4&s=48" width="48" height="48" alt="anpoirier" title="anpoirier"/></a> <a href="https://github.com/Asleep123"><img src="https://avatars.githubusercontent.com/u/122379135?v=4&s=48" width="48" height="48" alt="Asleep123" title="Asleep123"/></a> <a href="https://github.com/bolismauro"><img src="https://avatars.githubusercontent.com/u/771999?v=4&s=48" width="48" height="48" alt="bolismauro" title="bolismauro"/></a>
|
<a href="https://github.com/search?q=ClawdFx"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ClawdFx" title="ClawdFx"/></a> <a href="https://github.com/dguido"><img src="https://avatars.githubusercontent.com/u/294844?v=4&s=48" width="48" height="48" alt="dguido" title="dguido"/></a> <a href="https://github.com/EnzeD"><img src="https://avatars.githubusercontent.com/u/9866900?v=4&s=48" width="48" height="48" alt="EnzeD" title="EnzeD"/></a> <a href="https://github.com/erik-agens"><img src="https://avatars.githubusercontent.com/u/80908960?v=4&s=48" width="48" height="48" alt="erik-agens" title="erik-agens"/></a> <a href="https://github.com/Evizero"><img src="https://avatars.githubusercontent.com/u/10854026?v=4&s=48" width="48" height="48" alt="Evizero" title="Evizero"/></a> <a href="https://github.com/fcatuhe"><img src="https://avatars.githubusercontent.com/u/17382215?v=4&s=48" width="48" height="48" alt="fcatuhe" title="fcatuhe"/></a> <a href="https://github.com/itsjaydesu"><img src="https://avatars.githubusercontent.com/u/220390?v=4&s=48" width="48" height="48" alt="itsjaydesu" title="itsjaydesu"/></a> <a href="https://github.com/ivancasco"><img src="https://avatars.githubusercontent.com/u/2452858?v=4&s=48" width="48" height="48" alt="ivancasco" title="ivancasco"/></a> <a href="https://github.com/ivanrvpereira"><img src="https://avatars.githubusercontent.com/u/183991?v=4&s=48" width="48" height="48" alt="ivanrvpereira" title="ivanrvpereira"/></a> <a href="https://github.com/jayhickey"><img src="https://avatars.githubusercontent.com/u/1676460?v=4&s=48" width="48" height="48" alt="jayhickey" title="jayhickey"/></a>
|
||||||
<a href="https://github.com/conhecendoia"><img src="https://avatars.githubusercontent.com/u/82890727?v=4&s=48" width="48" height="48" alt="conhecendoia" title="conhecendoia"/></a> <a href="https://github.com/search?q=Dimitrios%20Ploutarchos"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Dimitrios Ploutarchos" title="Dimitrios Ploutarchos"/></a> <a href="https://github.com/search?q=Drake%20Thomsen"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Drake Thomsen" title="Drake Thomsen"/></a> <a href="https://github.com/Evizero"><img src="https://avatars.githubusercontent.com/u/10854026?v=4&s=48" width="48" height="48" alt="Evizero" title="Evizero"/></a> <a href="https://github.com/fal3"><img src="https://avatars.githubusercontent.com/u/6484295?v=4&s=48" width="48" height="48" alt="fal3" title="fal3"/></a> <a href="https://github.com/search?q=Felix%20Krause"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Felix Krause" title="Felix Krause"/></a> <a href="https://github.com/search?q=ganghyun%20kim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ganghyun kim" title="ganghyun kim"/></a> <a href="https://github.com/gtsifrikas"><img src="https://avatars.githubusercontent.com/u/8904378?v=4&s=48" width="48" height="48" alt="gtsifrikas" title="gtsifrikas"/></a> <a href="https://github.com/HazAT"><img src="https://avatars.githubusercontent.com/u/363802?v=4&s=48" width="48" height="48" alt="HazAT" title="HazAT"/></a> <a href="https://github.com/hrdwdmrbl"><img src="https://avatars.githubusercontent.com/u/554881?v=4&s=48" width="48" height="48" alt="hrdwdmrbl" title="hrdwdmrbl"/></a>
|
<a href="https://github.com/jeffersonwarrior"><img src="https://avatars.githubusercontent.com/u/89030989?v=4&s=48" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a> <a href="https://github.com/search?q=jeffersonwarrior"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a> <a href="https://github.com/jverdi"><img src="https://avatars.githubusercontent.com/u/345050?v=4&s=48" width="48" height="48" alt="jverdi" title="jverdi"/></a> <a href="https://github.com/longmaba"><img src="https://avatars.githubusercontent.com/u/9361500?v=4&s=48" width="48" height="48" alt="longmaba" title="longmaba"/></a> <a href="https://github.com/mickahouan"><img src="https://avatars.githubusercontent.com/u/31423109?v=4&s=48" width="48" height="48" alt="mickahouan" title="mickahouan"/></a> <a href="https://github.com/mjrussell"><img src="https://avatars.githubusercontent.com/u/1641895?v=4&s=48" width="48" height="48" alt="mjrussell" title="mjrussell"/></a> <a href="https://github.com/odnxe"><img src="https://avatars.githubusercontent.com/u/403141?v=4&s=48" width="48" height="48" alt="odnxe" title="odnxe"/></a> <a href="https://github.com/p6l-richard"><img src="https://avatars.githubusercontent.com/u/18185649?v=4&s=48" width="48" height="48" alt="p6l-richard" title="p6l-richard"/></a> <a href="https://github.com/philipp-spiess"><img src="https://avatars.githubusercontent.com/u/458591?v=4&s=48" width="48" height="48" alt="philipp-spiess" title="philipp-spiess"/></a> <a href="https://github.com/robaxelsen"><img src="https://avatars.githubusercontent.com/u/13132899?v=4&s=48" width="48" height="48" alt="robaxelsen" title="robaxelsen"/></a>
|
||||||
<a href="https://github.com/hugobarauna"><img src="https://avatars.githubusercontent.com/u/2719?v=4&s=48" width="48" height="48" alt="hugobarauna" title="hugobarauna"/></a> <a href="https://github.com/search?q=Jamie%20Openshaw"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jamie Openshaw" title="Jamie Openshaw"/></a> <a href="https://github.com/search?q=Jarvis"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jarvis" title="Jarvis"/></a> <a href="https://github.com/search?q=Jefferson%20Nunn"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jefferson Nunn" title="Jefferson Nunn"/></a> <a href="https://github.com/search?q=Kevin%20Lin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kevin Lin" title="Kevin Lin"/></a> <a href="https://github.com/kitze"><img src="https://avatars.githubusercontent.com/u/1160594?v=4&s=48" width="48" height="48" alt="kitze" title="kitze"/></a> <a href="https://github.com/levifig"><img src="https://avatars.githubusercontent.com/u/1605?v=4&s=48" width="48" height="48" alt="levifig" title="levifig"/></a> <a href="https://github.com/search?q=Lloyd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Lloyd" title="Lloyd"/></a> <a href="https://github.com/loukotal"><img src="https://avatars.githubusercontent.com/u/18210858?v=4&s=48" width="48" height="48" alt="loukotal" title="loukotal"/></a> <a href="https://github.com/martinpucik"><img src="https://avatars.githubusercontent.com/u/5503097?v=4&s=48" width="48" height="48" alt="martinpucik" title="martinpucik"/></a>
|
<a href="https://github.com/search?q=Sash%20Catanzarite"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Sash Catanzarite" title="Sash Catanzarite"/></a> <a href="https://github.com/T5-AndyML"><img src="https://avatars.githubusercontent.com/u/22801233?v=4&s=48" width="48" height="48" alt="T5-AndyML" title="T5-AndyML"/></a> <a href="https://github.com/travisp"><img src="https://avatars.githubusercontent.com/u/165698?v=4&s=48" width="48" height="48" alt="travisp" title="travisp"/></a> <a href="https://github.com/search?q=VAC"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="VAC" title="VAC"/></a> <a href="https://github.com/search?q=william%20arzt"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="william arzt" title="william arzt"/></a> <a href="https://github.com/zknicker"><img src="https://avatars.githubusercontent.com/u/1164085?v=4&s=48" width="48" height="48" alt="zknicker" title="zknicker"/></a> <a href="https://github.com/abhaymundhara"><img src="https://avatars.githubusercontent.com/u/62872231?v=4&s=48" width="48" height="48" alt="abhaymundhara" title="abhaymundhara"/></a> <a href="https://github.com/search?q=alejandro%20maza"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="alejandro maza" title="alejandro maza"/></a> <a href="https://github.com/Alex-Alaniz"><img src="https://avatars.githubusercontent.com/u/88956822?v=4&s=48" width="48" height="48" alt="Alex-Alaniz" title="Alex-Alaniz"/></a> <a href="https://github.com/andrewting19"><img src="https://avatars.githubusercontent.com/u/10536704?v=4&s=48" width="48" height="48" alt="andrewting19" title="andrewting19"/></a>
|
||||||
<a href="https://github.com/search?q=Matt%20mini"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Matt mini" title="Matt mini"/></a> <a href="https://github.com/search?q=Miles"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Miles" title="Miles"/></a> <a href="https://github.com/mrdbstn"><img src="https://avatars.githubusercontent.com/u/58957632?v=4&s=48" width="48" height="48" alt="mrdbstn" title="mrdbstn"/></a> <a href="https://github.com/MSch"><img src="https://avatars.githubusercontent.com/u/7475?v=4&s=48" width="48" height="48" alt="MSch" title="MSch"/></a> <a href="https://github.com/search?q=Mustafa%20Tag%20Eldeen"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mustafa Tag Eldeen" title="Mustafa Tag Eldeen"/></a> <a href="https://github.com/ndraiman"><img src="https://avatars.githubusercontent.com/u/12609607?v=4&s=48" width="48" height="48" alt="ndraiman" title="ndraiman"/></a> <a href="https://github.com/nexty5870"><img src="https://avatars.githubusercontent.com/u/3869659?v=4&s=48" width="48" height="48" alt="nexty5870" title="nexty5870"/></a> <a href="https://github.com/odnxe"><img src="https://avatars.githubusercontent.com/u/403141?v=4&s=48" width="48" height="48" alt="odnxe" title="odnxe"/></a> <a href="https://github.com/prathamdby"><img src="https://avatars.githubusercontent.com/u/134331217?v=4&s=48" width="48" height="48" alt="prathamdby" title="prathamdby"/></a> <a href="https://github.com/ptn1411"><img src="https://avatars.githubusercontent.com/u/57529765?v=4&s=48" width="48" height="48" alt="ptn1411" title="ptn1411"/></a>
|
<a href="https://github.com/anpoirier"><img src="https://avatars.githubusercontent.com/u/1245729?v=4&s=48" width="48" height="48" alt="anpoirier" title="anpoirier"/></a> <a href="https://github.com/arthyn"><img src="https://avatars.githubusercontent.com/u/5466421?v=4&s=48" width="48" height="48" alt="arthyn" title="arthyn"/></a> <a href="https://github.com/Asleep123"><img src="https://avatars.githubusercontent.com/u/122379135?v=4&s=48" width="48" height="48" alt="Asleep123" title="Asleep123"/></a> <a href="https://github.com/bolismauro"><img src="https://avatars.githubusercontent.com/u/771999?v=4&s=48" width="48" height="48" alt="bolismauro" title="bolismauro"/></a> <a href="https://github.com/conhecendoia"><img src="https://avatars.githubusercontent.com/u/82890727?v=4&s=48" width="48" height="48" alt="conhecendoia" title="conhecendoia"/></a> <a href="https://github.com/dasilva333"><img src="https://avatars.githubusercontent.com/u/947827?v=4&s=48" width="48" height="48" alt="dasilva333" title="dasilva333"/></a> <a href="https://github.com/search?q=Developer"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Developer" title="Developer"/></a> <a href="https://github.com/search?q=Dimitrios%20Ploutarchos"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Dimitrios Ploutarchos" title="Dimitrios Ploutarchos"/></a> <a href="https://github.com/search?q=Drake%20Thomsen"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Drake Thomsen" title="Drake Thomsen"/></a> <a href="https://github.com/fal3"><img src="https://avatars.githubusercontent.com/u/6484295?v=4&s=48" width="48" height="48" alt="fal3" title="fal3"/></a>
|
||||||
<a href="https://github.com/reeltimeapps"><img src="https://avatars.githubusercontent.com/u/637338?v=4&s=48" width="48" height="48" alt="reeltimeapps" title="reeltimeapps"/></a> <a href="https://github.com/RLTCmpe"><img src="https://avatars.githubusercontent.com/u/10762242?v=4&s=48" width="48" height="48" alt="RLTCmpe" title="RLTCmpe"/></a> <a href="https://github.com/search?q=Rolf%20Fredheim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rolf Fredheim" title="Rolf Fredheim"/></a> <a href="https://github.com/search?q=Rony%20Kelner"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rony Kelner" title="Rony Kelner"/></a> <a href="https://github.com/search?q=Samrat%20Jha"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Samrat Jha" title="Samrat Jha"/></a> <a href="https://github.com/shiv19"><img src="https://avatars.githubusercontent.com/u/9407019?v=4&s=48" width="48" height="48" alt="shiv19" title="shiv19"/></a> <a href="https://github.com/siraht"><img src="https://avatars.githubusercontent.com/u/73152895?v=4&s=48" width="48" height="48" alt="siraht" title="siraht"/></a> <a href="https://github.com/snopoke"><img src="https://avatars.githubusercontent.com/u/249606?v=4&s=48" width="48" height="48" alt="snopoke" title="snopoke"/></a> <a href="https://github.com/testingabc321"><img src="https://avatars.githubusercontent.com/u/8577388?v=4&s=48" width="48" height="48" alt="testingabc321" title="testingabc321"/></a> <a href="https://github.com/search?q=The%20Admiral"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="The Admiral" title="The Admiral"/></a>
|
<a href="https://github.com/search?q=Felix%20Krause"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Felix Krause" title="Felix Krause"/></a> <a href="https://github.com/foeken"><img src="https://avatars.githubusercontent.com/u/13864?v=4&s=48" width="48" height="48" alt="foeken" title="foeken"/></a> <a href="https://github.com/search?q=ganghyun%20kim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ganghyun kim" title="ganghyun kim"/></a> <a href="https://github.com/grrowl"><img src="https://avatars.githubusercontent.com/u/907140?v=4&s=48" width="48" height="48" alt="grrowl" title="grrowl"/></a> <a href="https://github.com/gtsifrikas"><img src="https://avatars.githubusercontent.com/u/8904378?v=4&s=48" width="48" height="48" alt="gtsifrikas" title="gtsifrikas"/></a> <a href="https://github.com/HazAT"><img src="https://avatars.githubusercontent.com/u/363802?v=4&s=48" width="48" height="48" alt="HazAT" title="HazAT"/></a> <a href="https://github.com/hrdwdmrbl"><img src="https://avatars.githubusercontent.com/u/554881?v=4&s=48" width="48" height="48" alt="hrdwdmrbl" title="hrdwdmrbl"/></a> <a href="https://github.com/hugobarauna"><img src="https://avatars.githubusercontent.com/u/2719?v=4&s=48" width="48" height="48" alt="hugobarauna" title="hugobarauna"/></a> <a href="https://github.com/search?q=Jamie%20Openshaw"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jamie Openshaw" title="Jamie Openshaw"/></a> <a href="https://github.com/search?q=Jarvis"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jarvis" title="Jarvis"/></a>
|
||||||
<a href="https://github.com/thesash"><img src="https://avatars.githubusercontent.com/u/1166151?v=4&s=48" width="48" height="48" alt="thesash" title="thesash"/></a> <a href="https://github.com/search?q=Ubuntu"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ubuntu" title="Ubuntu"/></a> <a href="https://github.com/voidserf"><img src="https://avatars.githubusercontent.com/u/477673?v=4&s=48" width="48" height="48" alt="voidserf" title="voidserf"/></a> <a href="https://github.com/search?q=Vultr-Clawd%20Admin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Vultr-Clawd Admin" title="Vultr-Clawd Admin"/></a> <a href="https://github.com/search?q=Wimmie"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Wimmie" title="Wimmie"/></a> <a href="https://github.com/wstock"><img src="https://avatars.githubusercontent.com/u/1394687?v=4&s=48" width="48" height="48" alt="wstock" title="wstock"/></a> <a href="https://github.com/yazinsai"><img src="https://avatars.githubusercontent.com/u/1846034?v=4&s=48" width="48" height="48" alt="yazinsai" title="yazinsai"/></a> <a href="https://github.com/search?q=Zach%20Knickerbocker"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Zach Knickerbocker" title="Zach Knickerbocker"/></a> <a href="https://github.com/Alphonse-arianee"><img src="https://avatars.githubusercontent.com/u/254457365?v=4&s=48" width="48" height="48" alt="Alphonse-arianee" title="Alphonse-arianee"/></a> <a href="https://github.com/search?q=Azade"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Azade" title="Azade"/></a>
|
<a href="https://github.com/search?q=Jefferson%20Nunn"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jefferson Nunn" title="Jefferson Nunn"/></a> <a href="https://github.com/search?q=Kevin%20Lin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kevin Lin" title="Kevin Lin"/></a> <a href="https://github.com/kitze"><img src="https://avatars.githubusercontent.com/u/1160594?v=4&s=48" width="48" height="48" alt="kitze" title="kitze"/></a> <a href="https://github.com/levifig"><img src="https://avatars.githubusercontent.com/u/1605?v=4&s=48" width="48" height="48" alt="levifig" title="levifig"/></a> <a href="https://github.com/search?q=Lloyd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Lloyd" title="Lloyd"/></a> <a href="https://github.com/loukotal"><img src="https://avatars.githubusercontent.com/u/18210858?v=4&s=48" width="48" height="48" alt="loukotal" title="loukotal"/></a> <a href="https://github.com/louzhixian"><img src="https://avatars.githubusercontent.com/u/7994361?v=4&s=48" width="48" height="48" alt="louzhixian" title="louzhixian"/></a> <a href="https://github.com/martinpucik"><img src="https://avatars.githubusercontent.com/u/5503097?v=4&s=48" width="48" height="48" alt="martinpucik" title="martinpucik"/></a> <a href="https://github.com/search?q=Matt%20mini"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Matt mini" title="Matt mini"/></a> <a href="https://github.com/mertcicekci0"><img src="https://avatars.githubusercontent.com/u/179321902?v=4&s=48" width="48" height="48" alt="mertcicekci0" title="mertcicekci0"/></a>
|
||||||
<a href="https://github.com/carlulsoe"><img src="https://avatars.githubusercontent.com/u/34673973?v=4&s=48" width="48" height="48" alt="carlulsoe" title="carlulsoe"/></a> <a href="https://github.com/search?q=ddyo"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ddyo" title="ddyo"/></a> <a href="https://github.com/search?q=Erik"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Erik" title="Erik"/></a> <a href="https://github.com/latitudeki5223"><img src="https://avatars.githubusercontent.com/u/119656367?v=4&s=48" width="48" height="48" alt="latitudeki5223" title="latitudeki5223"/></a> <a href="https://github.com/search?q=Manuel%20Maly"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Manuel Maly" title="Manuel Maly"/></a> <a href="https://github.com/search?q=Mourad%20Boustani"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mourad Boustani" title="Mourad Boustani"/></a> <a href="https://github.com/odrobnik"><img src="https://avatars.githubusercontent.com/u/333270?v=4&s=48" width="48" height="48" alt="odrobnik" title="odrobnik"/></a> <a href="https://github.com/pcty-nextgen-ios-builder"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="pcty-nextgen-ios-builder" title="pcty-nextgen-ios-builder"/></a> <a href="https://github.com/search?q=Quentin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Quentin" title="Quentin"/></a> <a href="https://github.com/search?q=Randy%20Torres"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Randy Torres" title="Randy Torres"/></a>
|
<a href="https://github.com/search?q=Miles"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Miles" title="Miles"/></a> <a href="https://github.com/mrdbstn"><img src="https://avatars.githubusercontent.com/u/58957632?v=4&s=48" width="48" height="48" alt="mrdbstn" title="mrdbstn"/></a> <a href="https://github.com/MSch"><img src="https://avatars.githubusercontent.com/u/7475?v=4&s=48" width="48" height="48" alt="MSch" title="MSch"/></a> <a href="https://github.com/search?q=Mustafa%20Tag%20Eldeen"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mustafa Tag Eldeen" title="Mustafa Tag Eldeen"/></a> <a href="https://github.com/ndraiman"><img src="https://avatars.githubusercontent.com/u/12609607?v=4&s=48" width="48" height="48" alt="ndraiman" title="ndraiman"/></a> <a href="https://github.com/nexty5870"><img src="https://avatars.githubusercontent.com/u/3869659?v=4&s=48" width="48" height="48" alt="nexty5870" title="nexty5870"/></a> <a href="https://github.com/Noctivoro"><img src="https://avatars.githubusercontent.com/u/183974570?v=4&s=48" width="48" height="48" alt="Noctivoro" title="Noctivoro"/></a> <a href="https://github.com/prathamdby"><img src="https://avatars.githubusercontent.com/u/134331217?v=4&s=48" width="48" height="48" alt="prathamdby" title="prathamdby"/></a> <a href="https://github.com/ptn1411"><img src="https://avatars.githubusercontent.com/u/57529765?v=4&s=48" width="48" height="48" alt="ptn1411" title="ptn1411"/></a> <a href="https://github.com/reeltimeapps"><img src="https://avatars.githubusercontent.com/u/637338?v=4&s=48" width="48" height="48" alt="reeltimeapps" title="reeltimeapps"/></a>
|
||||||
<a href="https://github.com/rhjoh"><img src="https://avatars.githubusercontent.com/u/105699450?v=4&s=48" width="48" height="48" alt="rhjoh" title="rhjoh"/></a> <a href="https://github.com/ronak-guliani"><img src="https://avatars.githubusercontent.com/u/23518228?v=4&s=48" width="48" height="48" alt="ronak-guliani" title="ronak-guliani"/></a> <a href="https://github.com/search?q=William%20Stock"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="William Stock" title="William Stock"/></a>
|
<a href="https://github.com/RLTCmpe"><img src="https://avatars.githubusercontent.com/u/10762242?v=4&s=48" width="48" height="48" alt="RLTCmpe" title="RLTCmpe"/></a> <a href="https://github.com/search?q=Rolf%20Fredheim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rolf Fredheim" title="Rolf Fredheim"/></a> <a href="https://github.com/search?q=Rony%20Kelner"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rony Kelner" title="Rony Kelner"/></a> <a href="https://github.com/search?q=Samrat%20Jha"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Samrat Jha" title="Samrat Jha"/></a> <a href="https://github.com/senoldogann"><img src="https://avatars.githubusercontent.com/u/45736551?v=4&s=48" width="48" height="48" alt="senoldogann" title="senoldogann"/></a> <a href="https://github.com/Seredeep"><img src="https://avatars.githubusercontent.com/u/22802816?v=4&s=48" width="48" height="48" alt="Seredeep" title="Seredeep"/></a> <a href="https://github.com/sergical"><img src="https://avatars.githubusercontent.com/u/3760543?v=4&s=48" width="48" height="48" alt="sergical" title="sergical"/></a> <a href="https://github.com/shiv19"><img src="https://avatars.githubusercontent.com/u/9407019?v=4&s=48" width="48" height="48" alt="shiv19" title="shiv19"/></a> <a href="https://github.com/shiyuanhai"><img src="https://avatars.githubusercontent.com/u/1187370?v=4&s=48" width="48" height="48" alt="shiyuanhai" title="shiyuanhai"/></a> <a href="https://github.com/siraht"><img src="https://avatars.githubusercontent.com/u/73152895?v=4&s=48" width="48" height="48" alt="siraht" title="siraht"/></a>
|
||||||
|
<a href="https://github.com/snopoke"><img src="https://avatars.githubusercontent.com/u/249606?v=4&s=48" width="48" height="48" alt="snopoke" title="snopoke"/></a> <a href="https://github.com/testingabc321"><img src="https://avatars.githubusercontent.com/u/8577388?v=4&s=48" width="48" height="48" alt="testingabc321" title="testingabc321"/></a> <a href="https://github.com/search?q=The%20Admiral"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="The Admiral" title="The Admiral"/></a> <a href="https://github.com/thesash"><img src="https://avatars.githubusercontent.com/u/1166151?v=4&s=48" width="48" height="48" alt="thesash" title="thesash"/></a> <a href="https://github.com/search?q=Ubuntu"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ubuntu" title="Ubuntu"/></a> <a href="https://github.com/voidserf"><img src="https://avatars.githubusercontent.com/u/477673?v=4&s=48" width="48" height="48" alt="voidserf" title="voidserf"/></a> <a href="https://github.com/search?q=Vultr-Clawd%20Admin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Vultr-Clawd Admin" title="Vultr-Clawd Admin"/></a> <a href="https://github.com/search?q=Wimmie"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Wimmie" title="Wimmie"/></a> <a href="https://github.com/wstock"><img src="https://avatars.githubusercontent.com/u/1394687?v=4&s=48" width="48" height="48" alt="wstock" title="wstock"/></a> <a href="https://github.com/yazinsai"><img src="https://avatars.githubusercontent.com/u/1846034?v=4&s=48" width="48" height="48" alt="yazinsai" title="yazinsai"/></a>
|
||||||
|
<a href="https://github.com/search?q=ymat19"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ymat19" title="ymat19"/></a> <a href="https://github.com/search?q=Zach%20Knickerbocker"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Zach Knickerbocker" title="Zach Knickerbocker"/></a> <a href="https://github.com/0xJonHoldsCrypto"><img src="https://avatars.githubusercontent.com/u/81202085?v=4&s=48" width="48" height="48" alt="0xJonHoldsCrypto" title="0xJonHoldsCrypto"/></a> <a href="https://github.com/aaronn"><img src="https://avatars.githubusercontent.com/u/1653630?v=4&s=48" width="48" height="48" alt="aaronn" title="aaronn"/></a> <a href="https://github.com/Alphonse-arianee"><img src="https://avatars.githubusercontent.com/u/254457365?v=4&s=48" width="48" height="48" alt="Alphonse-arianee" title="Alphonse-arianee"/></a> <a href="https://github.com/atalovesyou"><img src="https://avatars.githubusercontent.com/u/3534502?v=4&s=48" width="48" height="48" alt="atalovesyou" title="atalovesyou"/></a> <a href="https://github.com/search?q=Azade"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Azade" title="Azade"/></a> <a href="https://github.com/carlulsoe"><img src="https://avatars.githubusercontent.com/u/34673973?v=4&s=48" width="48" height="48" alt="carlulsoe" title="carlulsoe"/></a> <a href="https://github.com/search?q=ddyo"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ddyo" title="ddyo"/></a> <a href="https://github.com/search?q=Erik"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Erik" title="Erik"/></a>
|
||||||
|
<a href="https://github.com/hougangdev"><img src="https://avatars.githubusercontent.com/u/105773686?v=4&s=48" width="48" height="48" alt="hougangdev" title="hougangdev"/></a> <a href="https://github.com/latitudeki5223"><img src="https://avatars.githubusercontent.com/u/119656367?v=4&s=48" width="48" height="48" alt="latitudeki5223" title="latitudeki5223"/></a> <a href="https://github.com/search?q=Manuel%20Maly"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Manuel Maly" title="Manuel Maly"/></a> <a href="https://github.com/search?q=Mourad%20Boustani"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mourad Boustani" title="Mourad Boustani"/></a> <a href="https://github.com/odrobnik"><img src="https://avatars.githubusercontent.com/u/333270?v=4&s=48" width="48" height="48" alt="odrobnik" title="odrobnik"/></a> <a href="https://github.com/pcty-nextgen-ios-builder"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="pcty-nextgen-ios-builder" title="pcty-nextgen-ios-builder"/></a> <a href="https://github.com/search?q=Quentin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Quentin" title="Quentin"/></a> <a href="https://github.com/search?q=Randy%20Torres"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Randy Torres" title="Randy Torres"/></a> <a href="https://github.com/rhjoh"><img src="https://avatars.githubusercontent.com/u/105699450?v=4&s=48" width="48" height="48" alt="rhjoh" title="rhjoh"/></a> <a href="https://github.com/ronak-guliani"><img src="https://avatars.githubusercontent.com/u/23518228?v=4&s=48" width="48" height="48" alt="ronak-guliani" title="ronak-guliani"/></a>
|
||||||
|
<a href="https://github.com/search?q=William%20Stock"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="William Stock" title="William Stock"/></a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
44
SECURITY.md
44
SECURITY.md
@@ -1,6 +1,6 @@
|
|||||||
# Security Policy
|
# Security Policy
|
||||||
|
|
||||||
If you believe you’ve found a security issue in Clawdbot, please report it privately.
|
If you believe you've found a security issue in Clawdbot, please report it privately.
|
||||||
|
|
||||||
## Reporting
|
## Reporting
|
||||||
|
|
||||||
@@ -13,3 +13,45 @@ For threat model + hardening guidance (including `clawdbot security audit --deep
|
|||||||
|
|
||||||
- `https://docs.clawd.bot/gateway/security`
|
- `https://docs.clawd.bot/gateway/security`
|
||||||
|
|
||||||
|
## Runtime Requirements
|
||||||
|
|
||||||
|
### Node.js Version
|
||||||
|
|
||||||
|
Clawdbot requires **Node.js 22.12.0 or later** (LTS). This version includes important security patches:
|
||||||
|
|
||||||
|
- CVE-2025-59466: async_hooks DoS vulnerability
|
||||||
|
- CVE-2026-21636: Permission model bypass vulnerability
|
||||||
|
|
||||||
|
Verify your Node.js version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node --version # Should be v22.12.0 or later
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Security
|
||||||
|
|
||||||
|
When running Clawdbot in Docker:
|
||||||
|
|
||||||
|
1. The official image runs as a non-root user (`node`) for reduced attack surface
|
||||||
|
2. Use `--read-only` flag when possible for additional filesystem protection
|
||||||
|
3. Limit container capabilities with `--cap-drop=ALL`
|
||||||
|
|
||||||
|
Example secure Docker run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --read-only --cap-drop=ALL \
|
||||||
|
-v clawdbot-data:/app/data \
|
||||||
|
clawdbot/clawdbot:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Scanning
|
||||||
|
|
||||||
|
This project uses `detect-secrets` for automated secret detection in CI/CD.
|
||||||
|
See `.detect-secrets.cfg` for configuration and `.secrets.baseline` for the baseline.
|
||||||
|
|
||||||
|
Run locally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install detect-secrets==1.5.0
|
||||||
|
detect-secrets scan --baseline .secrets.baseline
|
||||||
|
```
|
||||||
|
|||||||
217
appcast.xml
217
appcast.xml
@@ -2,6 +2,101 @@
|
|||||||
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
|
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
|
||||||
<channel>
|
<channel>
|
||||||
<title>Clawdbot</title>
|
<title>Clawdbot</title>
|
||||||
|
<item>
|
||||||
|
<title>2026.1.24-1</title>
|
||||||
|
<pubDate>Sun, 25 Jan 2026 14:05:25 +0000</pubDate>
|
||||||
|
<link>https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml</link>
|
||||||
|
<sparkle:version>7952</sparkle:version>
|
||||||
|
<sparkle:shortVersionString>2026.1.24-1</sparkle:shortVersionString>
|
||||||
|
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||||
|
<description><![CDATA[<h2>Clawdbot 2026.1.24-1</h2>
|
||||||
|
<h3>Fixes</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Packaging: include dist/shared output in npm tarball (fixes missing reasoning-tags import on install).</li>
|
||||||
|
</ul>
|
||||||
|
<p><a href="https://github.com/clawdbot/clawdbot/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||||
|
]]></description>
|
||||||
|
<enclosure url="https://github.com/clawdbot/clawdbot/releases/download/v2026.1.24-1/Clawdbot-2026.1.24-1.zip" length="12396699" type="application/octet-stream" sparkle:edSignature="VaEdWIgEJBrZLIp2UmigoQ6vaq4P/jNFXpHYXvXHD5MsATS0CqBl6ugyyxRq+/GbpUqmdgdlht4dTUVbLRw6BA=="/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title>2026.1.24</title>
|
||||||
|
<pubDate>Sun, 25 Jan 2026 13:31:05 +0000</pubDate>
|
||||||
|
<link>https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml</link>
|
||||||
|
<sparkle:version>7944</sparkle:version>
|
||||||
|
<sparkle:shortVersionString>2026.1.24</sparkle:shortVersionString>
|
||||||
|
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||||
|
<description><![CDATA[<h2>Clawdbot 2026.1.24</h2>
|
||||||
|
<h3>Highlights</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Providers: Ollama discovery + docs; Venice guide upgrades + cross-links. (#1606) Thanks @abhaymundhara. https://docs.clawd.bot/providers/ollama https://docs.clawd.bot/providers/venice</li>
|
||||||
|
<li>Channels: LINE plugin (Messaging API) with rich replies + quick replies. (#1630) Thanks @plum-dawg.</li>
|
||||||
|
<li>TTS: Edge fallback (keyless) + <code>/tts</code> auto modes. (#1668, #1667) Thanks @steipete, @sebslight. https://docs.clawd.bot/tts</li>
|
||||||
|
<li>Exec approvals: approve in-chat via <code>/approve</code> across all channels (including plugins). (#1621) Thanks @czekaj. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/slash-commands</li>
|
||||||
|
<li>Telegram: DM topics as separate sessions + outbound link preview toggle. (#1597, #1700) Thanks @rohannagpal, @zerone0x. https://docs.clawd.bot/channels/telegram</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Changes</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Channels: add LINE plugin (Messaging API) with rich replies, quick replies, and plugin HTTP registry. (#1630) Thanks @plum-dawg.</li>
|
||||||
|
<li>TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.clawd.bot/tts</li>
|
||||||
|
<li>TTS: add auto mode enum (off/always/inbound/tagged) with per-session <code>/tts</code> override. (#1667) Thanks @sebslight. https://docs.clawd.bot/tts</li>
|
||||||
|
<li>Telegram: treat DM topics as separate sessions and keep DM history limits stable with thread suffixes. (#1597) Thanks @rohannagpal.</li>
|
||||||
|
<li>Telegram: add <code>channels.telegram.linkPreview</code> to toggle outbound link previews. (#1700) Thanks @zerone0x. https://docs.clawd.bot/channels/telegram</li>
|
||||||
|
<li>Web search: add Brave freshness filter parameter for time-scoped results. (#1688) Thanks @JonUleis. https://docs.clawd.bot/tools/web</li>
|
||||||
|
<li>UI: refresh Control UI dashboard design system (typography, colors, spacing). (#1786) Thanks @mousberg.</li>
|
||||||
|
<li>Exec approvals: forward approval prompts to chat with <code>/approve</code> for all channels (including plugins). (#1621) Thanks @czekaj. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/slash-commands</li>
|
||||||
|
<li>Gateway: expose config.patch in the gateway tool with safe partial updates + restart sentinel. (#1653) Thanks @Glucksberg.</li>
|
||||||
|
<li>Diagnostics: add diagnostic flags for targeted debug logs (config + env override). https://docs.clawd.bot/diagnostics/flags</li>
|
||||||
|
<li>Docs: expand FAQ (migration, scheduling, concurrency, model recommendations, OpenAI subscription auth, Pi sizing, hackable install, docs SSL workaround).</li>
|
||||||
|
<li>Docs: add verbose installer troubleshooting guidance.</li>
|
||||||
|
<li>Docs: add macOS VM guide with local/hosted options + VPS/nodes guidance. (#1693) Thanks @f-trycua.</li>
|
||||||
|
<li>Docs: add Bedrock EC2 instance role setup + IAM steps. (#1625) Thanks @sergical. https://docs.clawd.bot/bedrock</li>
|
||||||
|
<li>Docs: update Fly.io guide notes.</li>
|
||||||
|
<li>Dev: add prek pre-commit hooks + dependabot config for weekly updates. (#1720) Thanks @dguido.</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Fixes</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Web UI: fix config/debug layout overflow, scrolling, and code block sizing. (#1715) Thanks @saipreetham589.</li>
|
||||||
|
<li>Web UI: show Stop button during active runs, swap back to New session when idle. (#1664) Thanks @ndbroadbent.</li>
|
||||||
|
<li>Web UI: clear stale disconnect banners on reconnect; allow form saves with unsupported schema paths but block missing schema. (#1707) Thanks @Glucksberg.</li>
|
||||||
|
<li>Web UI: hide internal <code>message_id</code> hints in chat bubbles.</li>
|
||||||
|
<li>Gateway: allow Control UI token-only auth to skip device pairing even when device identity is present (<code>gateway.controlUi.allowInsecureAuth</code>). (#1679) Thanks @steipete.</li>
|
||||||
|
<li>Matrix: decrypt E2EE media attachments with preflight size guard. (#1744) Thanks @araa47.</li>
|
||||||
|
<li>BlueBubbles: route phone-number targets to DMs, avoid leaking routing IDs, and auto-create missing DMs (Private API required). (#1751) Thanks @tyler6204. https://docs.clawd.bot/channels/bluebubbles</li>
|
||||||
|
<li>BlueBubbles: keep part-index GUIDs in reply tags when short IDs are missing.</li>
|
||||||
|
<li>Signal: repair reaction sends (group/UUID targets + CLI author flags). (#1651) Thanks @vilkasdev.</li>
|
||||||
|
<li>Signal: add configurable signal-cli startup timeout + external daemon mode docs. (#1677) https://docs.clawd.bot/channels/signal</li>
|
||||||
|
<li>Telegram: set fetch duplex="half" for uploads on Node 22 to avoid sendPhoto failures. (#1684) Thanks @commdata2338.</li>
|
||||||
|
<li>Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639)</li>
|
||||||
|
<li>Telegram: honor per-account proxy for outbound API calls. (#1774) Thanks @radek-paclt.</li>
|
||||||
|
<li>Telegram: fall back to text when voice notes are blocked by privacy settings. (#1725) Thanks @foeken.</li>
|
||||||
|
<li>Voice Call: return stream TwiML for outbound conversation calls on initial Twilio webhook. (#1634)</li>
|
||||||
|
<li>Voice Call: serialize Twilio TTS playback and cancel on barge-in to prevent overlap. (#1713) Thanks @dguido.</li>
|
||||||
|
<li>Google Chat: tighten email allowlist matching, typing cleanup, media caps, and onboarding/docs/tests. (#1635) Thanks @iHildy.</li>
|
||||||
|
<li>Google Chat: normalize space targets without double <code>spaces/</code> prefix.</li>
|
||||||
|
<li>Agents: auto-compact on context overflow prompt errors before failing. (#1627) Thanks @rodrigouroz.</li>
|
||||||
|
<li>Agents: use the active auth profile for auto-compaction recovery.</li>
|
||||||
|
<li>Media understanding: skip image understanding when the primary model already supports vision. (#1747) Thanks @tyler6204.</li>
|
||||||
|
<li>Models: default missing custom provider fields so minimal configs are accepted.</li>
|
||||||
|
<li>Messaging: keep newline chunking safe for fenced markdown blocks across channels.</li>
|
||||||
|
<li>TUI: reload history after gateway reconnect to restore session state. (#1663)</li>
|
||||||
|
<li>Heartbeat: normalize target identifiers for consistent routing.</li>
|
||||||
|
<li>Exec: keep approvals for elevated ask unless full mode. (#1616) Thanks @ivancasco.</li>
|
||||||
|
<li>Exec: treat Windows platform labels as Windows for node shell selection. (#1760) Thanks @ymat19.</li>
|
||||||
|
<li>Gateway: include inline config env vars in service install environments. (#1735) Thanks @Seredeep.</li>
|
||||||
|
<li>Gateway: skip Tailscale DNS probing when tailscale.mode is off. (#1671)</li>
|
||||||
|
<li>Gateway: reduce log noise for late invokes + remote node probes; debounce skills refresh. (#1607) Thanks @petter-b.</li>
|
||||||
|
<li>Gateway: clarify Control UI/WebChat auth error hints for missing tokens. (#1690)</li>
|
||||||
|
<li>Gateway: listen on IPv6 loopback when bound to 127.0.0.1 so localhost webhooks work.</li>
|
||||||
|
<li>Gateway: store lock files in the temp directory to avoid stale locks on persistent volumes. (#1676)</li>
|
||||||
|
<li>macOS: default direct-transport <code>ws://</code> URLs to port 18789; document <code>gateway.remote.transport</code>. (#1603) Thanks @ngutman.</li>
|
||||||
|
<li>Tests: cap Vitest workers on CI macOS to reduce timeouts. (#1597) Thanks @rohannagpal.</li>
|
||||||
|
<li>Tests: avoid fake-timer dependency in embedded runner stream mock to reduce CI flakes. (#1597) Thanks @rohannagpal.</li>
|
||||||
|
<li>Tests: increase embedded runner ordering test timeout to reduce CI flakes. (#1597) Thanks @rohannagpal.</li>
|
||||||
|
</ul>
|
||||||
|
<p><a href="https://github.com/clawdbot/clawdbot/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||||
|
]]></description>
|
||||||
|
<enclosure url="https://github.com/clawdbot/clawdbot/releases/download/v2026.1.24/Clawdbot-2026.1.24.zip" length="12396700" type="application/octet-stream" sparkle:edSignature="u+XzKD3YwV8s79gIr7LK4OtDCcmp/b+cjNC6SHav3/1CVJegh02SsBKatrampox32XGx8P2+8c/+fHV+qpkHCA=="/>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>2026.1.23</title>
|
<title>2026.1.23</title>
|
||||||
<pubDate>Sat, 24 Jan 2026 13:02:18 +0000</pubDate>
|
<pubDate>Sat, 24 Jan 2026 13:02:18 +0000</pubDate>
|
||||||
@@ -89,127 +184,5 @@
|
|||||||
]]></description>
|
]]></description>
|
||||||
<enclosure url="https://github.com/clawdbot/clawdbot/releases/download/v2026.1.23/Clawdbot-2026.1.23.zip" length="22326233" type="application/octet-stream" sparkle:edSignature="p40dFczUfmMpsif4BrEUYVqUPG2WiBXleWgefwu4WiqjuyXbw7CAaH5CpQKig/k2qRLlE59kX7AR/qJqmy+yCA=="/>
|
<enclosure url="https://github.com/clawdbot/clawdbot/releases/download/v2026.1.23/Clawdbot-2026.1.23.zip" length="22326233" type="application/octet-stream" sparkle:edSignature="p40dFczUfmMpsif4BrEUYVqUPG2WiBXleWgefwu4WiqjuyXbw7CAaH5CpQKig/k2qRLlE59kX7AR/qJqmy+yCA=="/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<title>2026.1.22</title>
|
|
||||||
<pubDate>Fri, 23 Jan 2026 08:58:14 +0000</pubDate>
|
|
||||||
<link>https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml</link>
|
|
||||||
<sparkle:version>7530</sparkle:version>
|
|
||||||
<sparkle:shortVersionString>2026.1.22</sparkle:shortVersionString>
|
|
||||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
|
||||||
<description><![CDATA[<h2>Clawdbot 2026.1.22</h2>
|
|
||||||
<h3>Changes</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Highlight: Compaction safeguard now uses adaptive chunking, progressive fallback, and UI status + retries. (#1466) Thanks @dlauer.</li>
|
|
||||||
<li>Providers: add Antigravity usage tracking to status output. (#1490) Thanks @patelhiren.</li>
|
|
||||||
<li>Slack: add chat-type reply threading overrides via <code>replyToModeByChatType</code>. (#1442) Thanks @stefangalescu.</li>
|
|
||||||
<li>BlueBubbles: add <code>asVoice</code> support for MP3/CAF voice memos in sendAttachment. (#1477, #1482) Thanks @Nicell.</li>
|
|
||||||
<li>Onboarding: add hatch choice (TUI/Web/Later), token explainer, background dashboard seed on macOS, and showcase link.</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Fixes</h3>
|
|
||||||
<ul>
|
|
||||||
<li>BlueBubbles: stop typing indicator on idle/no-reply. (#1439) Thanks @Nicell.</li>
|
|
||||||
<li>Message tool: keep path/filePath as-is for send; hydrate buffers only for sendAttachment. (#1444) Thanks @hopyky.</li>
|
|
||||||
<li>Auto-reply: only report a model switch when session state is available. (#1465) Thanks @robbyczgw-cla.</li>
|
|
||||||
<li>Control UI: resolve local avatar URLs with basePath across injection + identity RPC. (#1457) Thanks @dlauer.</li>
|
|
||||||
<li>Agents: sanitize assistant history text to strip tool-call markers. (#1456) Thanks @zerone0x.</li>
|
|
||||||
<li>Discord: clarify Message Content Intent onboarding hint. (#1487) Thanks @kyleok.</li>
|
|
||||||
<li>Gateway: stop the service before uninstalling and fail if it remains loaded.</li>
|
|
||||||
<li>Agents: surface concrete API error details instead of generic AI service errors.</li>
|
|
||||||
<li>Exec: fall back to non-PTY when PTY spawn fails (EBADF). (#1484)</li>
|
|
||||||
<li>Exec approvals: allow per-segment allowlists for chained shell commands on gateway + node hosts. (#1458) Thanks @czekaj.</li>
|
|
||||||
<li>Agents: make OpenAI sessions image-sanitize-only; gate tool-id/repair sanitization by provider.</li>
|
|
||||||
<li>Doctor: honor CLAWDBOT_GATEWAY_TOKEN for auth checks and security audit token reuse. (#1448) Thanks @azade-c.</li>
|
|
||||||
<li>Agents: make tool summaries more readable and only show optional params when set.</li>
|
|
||||||
<li>Agents: honor SOUL.md guidance even when the file is nested or path-qualified. (#1434) Thanks @neooriginal.</li>
|
|
||||||
<li>Matrix (plugin): persist m.direct for resolved DMs and harden room fallback. (#1436, #1486) Thanks @sibbl.</li>
|
|
||||||
<li>CLI: prefer <code>~</code> for home paths in output.</li>
|
|
||||||
<li>Mattermost (plugin): enforce pairing/allowlist gating, keep @username targets, and clarify plugin-only docs. (#1428) Thanks @damoahdominic.</li>
|
|
||||||
<li>Agents: centralize transcript sanitization in the runner; keep <final> tags and error turns intact.</li>
|
|
||||||
<li>Auth: skip auth profiles in cooldown during initial selection and rotation. (#1316) Thanks @odrobnik.</li>
|
|
||||||
<li>Agents/TUI: honor user-pinned auth profiles during cooldown and preserve search picker ranking. (#1432) Thanks @tobiasbischoff.</li>
|
|
||||||
<li>Docs: fix gog auth services example to include docs scope. (#1454) Thanks @zerone0x.</li>
|
|
||||||
<li>Slack: reduce WebClient retries to avoid duplicate sends. (#1481)</li>
|
|
||||||
<li>Slack: read thread replies for message reads when threadId is provided (replies-only). (#1450) Thanks @rodrigouroz.</li>
|
|
||||||
<li>macOS: prefer linked channels in gateway summary to avoid false “not linked” status.</li>
|
|
||||||
<li>macOS/tests: fix gateway summary lookup after guard unwrap; prevent browser opens during tests. (ECID-1483)</li>
|
|
||||||
</ul>
|
|
||||||
<p><a href="https://github.com/clawdbot/clawdbot/blob/main/CHANGELOG.md">View full changelog</a></p>
|
|
||||||
]]></description>
|
|
||||||
<enclosure url="https://github.com/clawdbot/clawdbot/releases/download/v2026.1.22/Clawdbot-2026.1.22.zip" length="22302446" type="application/octet-stream" sparkle:edSignature="w/EzfwGBCRRuCg5vz8enIfYujxOZJWRw9PaunQ7gIafKwnBJSTtxcnkvMVwQsnBwB6VN5Tu2MPij7PjDFFX+CA=="/>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<title>2026.1.21</title>
|
|
||||||
<pubDate>Thu, 22 Jan 2026 12:22:35 +0000</pubDate>
|
|
||||||
<link>https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml</link>
|
|
||||||
<sparkle:version>7374</sparkle:version>
|
|
||||||
<sparkle:shortVersionString>2026.1.21</sparkle:shortVersionString>
|
|
||||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
|
||||||
<description><![CDATA[<h2>Clawdbot 2026.1.21</h2>
|
|
||||||
<h3>Highlights</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Lobster optional plugin tool for typed workflows + approval gates. https://docs.clawd.bot/tools/lobster</li>
|
|
||||||
<li>Custom assistant identity + avatars in the Control UI. https://docs.clawd.bot/cli/agents https://docs.clawd.bot/web/control-ui</li>
|
|
||||||
<li>Cache optimizations: cache-ttl pruning + defaults reduce token spend on cold requests. https://docs.clawd.bot/concepts/session-pruning</li>
|
|
||||||
<li>Exec approvals + elevated ask/full modes. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/elevated</li>
|
|
||||||
<li>Signal typing/read receipts + MSTeams attachments. https://docs.clawd.bot/channels/signal https://docs.clawd.bot/channels/msteams</li>
|
|
||||||
<li><code>/models</code> UX refresh + <code>clawdbot update wizard</code>. https://docs.clawd.bot/cli/models https://docs.clawd.bot/cli/update</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Changes</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Highlight: Lobster optional plugin tool for typed workflows + approval gates. https://docs.clawd.bot/tools/lobster (#1152) Thanks @vignesh07.</li>
|
|
||||||
<li>Agents/UI: add identity avatar config support and Control UI avatar rendering. (#1329, #1424) Thanks @dlauer. https://docs.clawd.bot/gateway/configuration https://docs.clawd.bot/cli/agents</li>
|
|
||||||
<li>Control UI: add custom assistant identity support and per-session identity display. (#1420) Thanks @robbyczgw-cla. https://docs.clawd.bot/web/control-ui</li>
|
|
||||||
<li>CLI: add <code>clawdbot update wizard</code> with interactive channel selection + restart prompts, plus preflight checks before rebasing. https://docs.clawd.bot/cli/update</li>
|
|
||||||
<li>Models/Commands: add <code>/models</code>, improve <code>/model</code> listing UX, and expand <code>clawdbot models</code> paging. (#1398) Thanks @vignesh07. https://docs.clawd.bot/cli/models</li>
|
|
||||||
<li>CLI: move gateway service commands under <code>clawdbot gateway</code>, flatten node service commands under <code>clawdbot node</code>, and add <code>gateway probe</code> for reachability. https://docs.clawd.bot/cli/gateway https://docs.clawd.bot/cli/node</li>
|
|
||||||
<li>Exec: add elevated ask/full modes, tighten allowlist gating, and render approvals tables on write. https://docs.clawd.bot/tools/elevated https://docs.clawd.bot/tools/exec-approvals</li>
|
|
||||||
<li>Exec approvals: default to local host, add gateway/node targeting + target details, support wildcard agent allowlists, and tighten allowlist parsing/safe bins. https://docs.clawd.bot/cli/approvals https://docs.clawd.bot/tools/exec-approvals</li>
|
|
||||||
<li>Heartbeat: allow explicit session keys and active hours. (#1256) Thanks @zknicker. https://docs.clawd.bot/gateway/heartbeat</li>
|
|
||||||
<li>Sessions: add per-channel idle durations via <code>sessions.channelIdleMinutes</code>. (#1353) Thanks @cash-echo-bot.</li>
|
|
||||||
<li>Nodes: run exec-style, expose PATH in status/describe, and bootstrap PATH for node-host execution. https://docs.clawd.bot/cli/node</li>
|
|
||||||
<li>Cache: add <code>cache.ttlPrune</code> mode and auth-aware defaults for cache TTL behavior.</li>
|
|
||||||
<li>Queue: add per-channel debounce overrides for auto-reply. https://docs.clawd.bot/concepts/queue</li>
|
|
||||||
<li>Discord: add wildcard channel config support. (#1334) Thanks @pvoo. https://docs.clawd.bot/channels/discord</li>
|
|
||||||
<li>Signal: add typing indicators and DM read receipts via signal-cli. https://docs.clawd.bot/channels/signal</li>
|
|
||||||
<li>MSTeams: add file uploads, adaptive cards, and attachment handling improvements. (#1410) Thanks @Evizero. https://docs.clawd.bot/channels/msteams</li>
|
|
||||||
<li>Onboarding: remove the run setup-token auth option (paste setup-token or reuse CLI creds instead).</li>
|
|
||||||
<li>macOS: refresh Settings (location access in Permissions, connection mode in menu, remove CLI install UI).</li>
|
|
||||||
<li>Diagnostics: add cache trace config for debugging. (#1370) Thanks @parubets.</li>
|
|
||||||
<li>Docs: Lobster guides + org URL updates, /model allowlist troubleshooting, Gmail message search examples, gateway.mode troubleshooting, prompt injection guidance, npm prefix/node CLI notes, control UI dev gatewayUrl note, tool_use FAQ, showcase video, and sharp/node-gyp workaround. (#1427, #1220, #1405) Thanks @vignesh07, @mbelinky.</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Breaking</h3>
|
|
||||||
<ul>
|
|
||||||
<li><strong>BREAKING:</strong> Control UI now rejects insecure HTTP without device identity by default. Use HTTPS (Tailscale Serve) or set <code>gateway.controlUi.allowInsecureAuth: true</code> to allow token-only auth. https://docs.clawd.bot/web/control-ui#insecure-http</li>
|
|
||||||
<li><strong>BREAKING:</strong> Envelope and system event timestamps now default to host-local time (was UTC) so agents don’t have to constantly convert.</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Fixes</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Streaming/Typing/Media: keep reply tags across streamed chunks, start typing indicators at run start, and accept MEDIA paths with spaces/tilde while preferring the message tool hint for image replies.</li>
|
|
||||||
<li>Agents/Providers: drop unsigned thinking blocks for Claude models (Google Antigravity) and enforce alphanumeric tool call ids for strict providers (Mistral/OpenRouter). (#1372) Thanks @zerone0x.</li>
|
|
||||||
<li>Exec approvals: treat main as the default agent, align node/gateway allowlist prechecks, validate resolved paths, avoid allowlist resolve races, and avoid null optional params. (#1417, #1414, #1425) Thanks @czekaj.</li>
|
|
||||||
<li>Exec/Windows: resolve Windows exec paths with extensions and handle safe-bin exe names.</li>
|
|
||||||
<li>Nodes/macOS: prompt on allowlist miss for node exec approvals, persist allowlist decisions, and flatten node invoke errors. (#1394) Thanks @ngutman.</li>
|
|
||||||
<li>Gateway: prevent multiple gateways from sharing the same config/state (singleton lock), keep auto bind loopback-first with explicit tailnet binding, and improve SSH auth handling. (#1380)</li>
|
|
||||||
<li>Control UI: remove the chat stop button, keep the composer aligned to the bottom edge, stabilize session previews, and refresh the debug panel on route-driven tab changes. (#1373) Thanks @yazinsai.</li>
|
|
||||||
<li>UI/config: export <code>SECTION_META</code> for config form modules. (#1418) Thanks @MaudeBot.</li>
|
|
||||||
<li>macOS: keep chat pinned during streaming replies, include Textual resources, respect wildcard exec approvals, allow SSH agent auth, and default distribution builds to universal binaries. (#1279, #1362, #1384, #1396) Thanks @ameno-, @JustYannicc.</li>
|
|
||||||
<li>BlueBubbles: resolve short message IDs safely, expose full IDs in templates, and harden short-id fetch wrappers. (#1369, #1387) Thanks @tyler6204.</li>
|
|
||||||
<li>Models/Configure: inherit session model overrides in threads/topics, map OpenCode Zen models to the correct APIs, narrow Anthropic OAuth allowlist handling, seed allowlist fallbacks, list the full catalog when no allowlist is set, and limit <code>/model</code> list output. (#1376, #1416)</li>
|
|
||||||
<li>Memory: prevent CLI hangs by deferring vector probes, add sqlite-vec/embedding timeouts, and make session memory indexing async.</li>
|
|
||||||
<li>Cron: cap reminder context history to 10 messages and honor <code>contextMessages</code>. (#1103) Thanks @mkbehr.</li>
|
|
||||||
<li>Cache: restore the 1h cache TTL option and reset the pruning window.</li>
|
|
||||||
<li>Zalo Personal: tolerate ANSI/log-prefixed JSON output from <code>zca</code>. (#1379) Thanks @ptn1411.</li>
|
|
||||||
<li>Browser: suppress Chrome restore prompts for managed profiles. (#1419) Thanks @jamesgroat.</li>
|
|
||||||
<li>Infra: preserve fetch helper methods/preconnect when wrapping abort signals and normalize Telegram fetch aborts.</li>
|
|
||||||
<li>Config/Doctor: avoid stack traces for invalid configs, log the config path, avoid WhatsApp config resurrection, and warn when <code>gateway.mode</code> is unset. (#900)</li>
|
|
||||||
<li>CLI: read Codex CLI account_id for workspace billing. (#1422) Thanks @aj47.</li>
|
|
||||||
<li>Logs/Status: align rolling log filenames with local time and report sandboxed runtime in <code>clawdbot status</code>. (#1343)</li>
|
|
||||||
<li>Embedded runner: persist injected history images so attachments aren’t reloaded each turn. (#1374) Thanks @Nicell.</li>
|
|
||||||
<li>Nodes/Subagents: include agent/node/gateway context in tool failure logs and ensure subagent list uses the command session.</li>
|
|
||||||
</ul>
|
|
||||||
<p><a href="https://github.com/clawdbot/clawdbot/blob/main/CHANGELOG.md">View full changelog</a></p>
|
|
||||||
]]></description>
|
|
||||||
<enclosure url="https://github.com/clawdbot/clawdbot/releases/download/v2026.1.21/Clawdbot-2026.1.21.zip" length="22284796" type="application/octet-stream" sparkle:edSignature="pXji4NMA/cu35iMxln385d6LnsT4yIZtFtFiR7sIimKeSC2CsyeWzzSD0EhJsN98PdSoy69iEFZt4I2ZtNCECg=="/>
|
|
||||||
</item>
|
|
||||||
</channel>
|
</channel>
|
||||||
</rss>
|
</rss>
|
||||||
@@ -21,8 +21,8 @@ android {
|
|||||||
applicationId = "com.clawdbot.android"
|
applicationId = "com.clawdbot.android"
|
||||||
minSdk = 31
|
minSdk = 31
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 202601240
|
versionCode = 202601250
|
||||||
versionName = "2026.1.24"
|
versionName = "2026.1.25"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -12,4 +12,3 @@ data class CameraHudState(
|
|||||||
val kind: CameraHudKind,
|
val kind: CameraHudKind,
|
||||||
val message: String,
|
val message: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -12,4 +12,3 @@ enum class VoiceWakeMode(val rawValue: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ class SmsManager(private val context: Context) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Send an SMS message.
|
* Send an SMS message.
|
||||||
*
|
*
|
||||||
* @param paramsJson JSON with "to" (phone number) and "message" (text) fields
|
* @param paramsJson JSON with "to" (phone number) and "message" (text) fields
|
||||||
* @return SendResult indicating success or failure
|
* @return SendResult indicating success or failure
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#0A0A0A</color>
|
<color name="ic_launcher_background">#0A0A0A</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Clawdbot Node</string>
|
<string name="app_name">Clawdbot Node</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|||||||
@@ -23,4 +23,3 @@ class VoiceWakeCommandExtractorTest {
|
|||||||
assertNull(VoiceWakeCommandExtractor.extractCommand("hey claude!", listOf("claude")))
|
assertNull(VoiceWakeCommandExtractor.extractCommand("hey claude!", listOf("claude")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,4 +16,3 @@ dependencyResolutionManagement {
|
|||||||
|
|
||||||
rootProject.name = "ClawdbotNodeAndroid"
|
rootProject.name = "ClawdbotNodeAndroid"
|
||||||
include(":app")
|
include(":app")
|
||||||
|
|
||||||
|
|||||||
@@ -3,4 +3,3 @@ parent_config: ../../.swiftlint.yml
|
|||||||
included:
|
included:
|
||||||
- Sources
|
- Sources
|
||||||
- ../shared/ClawdisNodeKit/Sources
|
- ../shared/ClawdisNodeKit/Sources
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.1.24</string>
|
<string>2026.1.25</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>20260124</string>
|
<string>20260125</string>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoadsInWebContent</key>
|
<key>NSAllowsArbitraryLoadsInWebContent</key>
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>BNDL</string>
|
<string>BNDL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.1.24</string>
|
<string>2026.1.25</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>20260124</string>
|
<string>20260125</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ targets:
|
|||||||
properties:
|
properties:
|
||||||
CFBundleDisplayName: Clawdbot
|
CFBundleDisplayName: Clawdbot
|
||||||
CFBundleIconName: AppIcon
|
CFBundleIconName: AppIcon
|
||||||
CFBundleShortVersionString: "2026.1.24"
|
CFBundleShortVersionString: "2026.1.25"
|
||||||
CFBundleVersion: "20260124"
|
CFBundleVersion: "20260125"
|
||||||
UILaunchScreen: {}
|
UILaunchScreen: {}
|
||||||
UIApplicationSceneManifest:
|
UIApplicationSceneManifest:
|
||||||
UIApplicationSupportsMultipleScenes: false
|
UIApplicationSupportsMultipleScenes: false
|
||||||
@@ -130,5 +130,5 @@ targets:
|
|||||||
path: Tests/Info.plist
|
path: Tests/Info.plist
|
||||||
properties:
|
properties:
|
||||||
CFBundleDisplayName: ClawdbotTests
|
CFBundleDisplayName: ClawdbotTests
|
||||||
CFBundleShortVersionString: "2026.1.24"
|
CFBundleShortVersionString: "2026.1.25"
|
||||||
CFBundleVersion: "20260124"
|
CFBundleVersion: "20260125"
|
||||||
|
|||||||
@@ -33,4 +33,4 @@
|
|||||||
],
|
],
|
||||||
"squares" : "shared"
|
"squares" : "shared"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,8 +123,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/gonzalezreal/textual",
|
"location" : "https://github.com/gonzalezreal/textual",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "a03c1e103d88de4ea0dd8320ea1611ec0d4b29b3",
|
"revision" : "5b06b811c0f5313b6b84bbef98c635a630638c38",
|
||||||
"version" : "0.2.0"
|
"version" : "0.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -413,10 +413,17 @@ final class AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateRemoteTarget(host: String) {
|
private func updateRemoteTarget(host: String) {
|
||||||
let parsed = CommandResolver.parseSSHTarget(self.remoteTarget)
|
let trimmed = self.remoteTarget.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let user = parsed?.user ?? NSUserName()
|
guard let parsed = CommandResolver.parseSSHTarget(trimmed) else { return }
|
||||||
let port = parsed?.port ?? 22
|
let trimmedUser = parsed.user?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let assembled = port == 22 ? "\(user)@\(host)" : "\(user)@\(host):\(port)"
|
let user = (trimmedUser?.isEmpty ?? true) ? nil : trimmedUser
|
||||||
|
let port = parsed.port
|
||||||
|
let assembled: String
|
||||||
|
if let user {
|
||||||
|
assembled = port == 22 ? "\(user)@\(host)" : "\(user)@\(host):\(port)"
|
||||||
|
} else {
|
||||||
|
assembled = port == 22 ? host : "\(host):\(port)"
|
||||||
|
}
|
||||||
if assembled != self.remoteTarget {
|
if assembled != self.remoteTarget {
|
||||||
self.remoteTarget = assembled
|
self.remoteTarget = assembled
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|||||||
@@ -173,4 +173,4 @@
|
|||||||
"iPod5,1": "iPod touch (5th generation)",
|
"iPod5,1": "iPod touch (5th generation)",
|
||||||
"iPod7,1": "iPod touch (6th generation)",
|
"iPod7,1": "iPod touch (6th generation)",
|
||||||
"iPod9,1": "iPod touch (7th generation)"
|
"iPod9,1": "iPod touch (7th generation)"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,4 +211,4 @@
|
|||||||
"Mac Pro (2019)",
|
"Mac Pro (2019)",
|
||||||
"Mac Pro (Rack, 2019)"
|
"Mac Pro (Rack, 2019)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.1.24</string>
|
<string>2026.1.25</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>202601240</string>
|
<string>202601250</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>Clawdbot</string>
|
<string>Clawdbot</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
|
|||||||
@@ -1167,17 +1167,29 @@ public struct ConfigApplyParams: Codable, Sendable {
|
|||||||
public struct ConfigPatchParams: Codable, Sendable {
|
public struct ConfigPatchParams: Codable, Sendable {
|
||||||
public let raw: String
|
public let raw: String
|
||||||
public let basehash: String?
|
public let basehash: String?
|
||||||
|
public let sessionkey: String?
|
||||||
|
public let note: String?
|
||||||
|
public let restartdelayms: Int?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
raw: String,
|
raw: String,
|
||||||
basehash: String?
|
basehash: String?,
|
||||||
|
sessionkey: String?,
|
||||||
|
note: String?,
|
||||||
|
restartdelayms: Int?
|
||||||
) {
|
) {
|
||||||
self.raw = raw
|
self.raw = raw
|
||||||
self.basehash = basehash
|
self.basehash = basehash
|
||||||
|
self.sessionkey = sessionkey
|
||||||
|
self.note = note
|
||||||
|
self.restartdelayms = restartdelayms
|
||||||
}
|
}
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case raw
|
case raw
|
||||||
case basehash = "baseHash"
|
case basehash = "baseHash"
|
||||||
|
case sessionkey = "sessionKey"
|
||||||
|
case note
|
||||||
|
case restartdelayms = "restartDelayMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ let package = Package(
|
|||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/steipete/ElevenLabsKit", exact: "0.1.0"),
|
.package(url: "https://github.com/steipete/ElevenLabsKit", exact: "0.1.0"),
|
||||||
.package(url: "https://github.com/gonzalezreal/textual", exact: "0.2.0"),
|
.package(url: "https://github.com/gonzalezreal/textual", exact: "0.3.1"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
|
|||||||
@@ -574,46 +574,22 @@ public actor GatewayChannelActor {
|
|||||||
params: [String: AnyCodable]?,
|
params: [String: AnyCodable]?,
|
||||||
timeoutMs: Double? = nil) async throws -> Data
|
timeoutMs: Double? = nil) async throws -> Data
|
||||||
{
|
{
|
||||||
do {
|
try await self.connectOrThrow(context: "gateway connect")
|
||||||
try await self.connect()
|
|
||||||
} catch {
|
|
||||||
throw self.wrap(error, context: "gateway connect")
|
|
||||||
}
|
|
||||||
let id = UUID().uuidString
|
|
||||||
let effectiveTimeout = timeoutMs ?? self.defaultRequestTimeoutMs
|
let effectiveTimeout = timeoutMs ?? self.defaultRequestTimeoutMs
|
||||||
// Encode request using the generated models to avoid JSONSerialization/ObjC bridging pitfalls.
|
let payload = try self.encodeRequest(method: method, params: params, kind: "request")
|
||||||
let paramsObject: ProtoAnyCodable? = params.map { entries in
|
|
||||||
let dict = entries.reduce(into: [String: ProtoAnyCodable]()) { dict, entry in
|
|
||||||
dict[entry.key] = ProtoAnyCodable(entry.value.value)
|
|
||||||
}
|
|
||||||
return ProtoAnyCodable(dict)
|
|
||||||
}
|
|
||||||
let frame = RequestFrame(
|
|
||||||
type: "req",
|
|
||||||
id: id,
|
|
||||||
method: method,
|
|
||||||
params: paramsObject)
|
|
||||||
let data: Data
|
|
||||||
do {
|
|
||||||
data = try self.encoder.encode(frame)
|
|
||||||
} catch {
|
|
||||||
self.logger.error(
|
|
||||||
"gateway request encode failed \(method, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
let response = try await withCheckedThrowingContinuation { (cont: CheckedContinuation<GatewayFrame, Error>) in
|
let response = try await withCheckedThrowingContinuation { (cont: CheckedContinuation<GatewayFrame, Error>) in
|
||||||
self.pending[id] = cont
|
self.pending[payload.id] = cont
|
||||||
Task { [weak self] in
|
Task { [weak self] in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
try? await Task.sleep(nanoseconds: UInt64(effectiveTimeout * 1_000_000))
|
try? await Task.sleep(nanoseconds: UInt64(effectiveTimeout * 1_000_000))
|
||||||
await self.timeoutRequest(id: id, timeoutMs: effectiveTimeout)
|
await self.timeoutRequest(id: payload.id, timeoutMs: effectiveTimeout)
|
||||||
}
|
}
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
try await self.task?.send(.data(data))
|
try await self.task?.send(.data(payload.data))
|
||||||
} catch {
|
} catch {
|
||||||
let wrapped = self.wrap(error, context: "gateway send \(method)")
|
let wrapped = self.wrap(error, context: "gateway send \(method)")
|
||||||
let waiter = self.pending.removeValue(forKey: id)
|
let waiter = self.pending.removeValue(forKey: payload.id)
|
||||||
// Treat send failures as a broken socket: mark disconnected and trigger reconnect.
|
// Treat send failures as a broken socket: mark disconnected and trigger reconnect.
|
||||||
self.connected = false
|
self.connected = false
|
||||||
self.task?.cancel(with: .goingAway, reason: nil)
|
self.task?.cancel(with: .goingAway, reason: nil)
|
||||||
@@ -643,6 +619,29 @@ public actor GatewayChannelActor {
|
|||||||
return Data() // Should not happen, but tolerate empty payloads.
|
return Data() // Should not happen, but tolerate empty payloads.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func send(method: String, params: [String: AnyCodable]?) async throws {
|
||||||
|
try await self.connectOrThrow(context: "gateway connect")
|
||||||
|
let payload = try self.encodeRequest(method: method, params: params, kind: "send")
|
||||||
|
guard let task = self.task else {
|
||||||
|
throw NSError(
|
||||||
|
domain: "Gateway",
|
||||||
|
code: 5,
|
||||||
|
userInfo: [NSLocalizedDescriptionKey: "gateway socket unavailable"])
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
try await task.send(.data(payload.data))
|
||||||
|
} catch {
|
||||||
|
let wrapped = self.wrap(error, context: "gateway send \(method)")
|
||||||
|
self.connected = false
|
||||||
|
self.task?.cancel(with: .goingAway, reason: nil)
|
||||||
|
Task { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
await self.scheduleReconnect()
|
||||||
|
}
|
||||||
|
throw wrapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Wrap low-level URLSession/WebSocket errors with context so UI can surface them.
|
// Wrap low-level URLSession/WebSocket errors with context so UI can surface them.
|
||||||
private func wrap(_ error: Error, context: String) -> Error {
|
private func wrap(_ error: Error, context: String) -> Error {
|
||||||
if let urlError = error as? URLError {
|
if let urlError = error as? URLError {
|
||||||
@@ -657,6 +656,42 @@ public actor GatewayChannelActor {
|
|||||||
return NSError(domain: ns.domain, code: ns.code, userInfo: [NSLocalizedDescriptionKey: "\(context): \(desc)"])
|
return NSError(domain: ns.domain, code: ns.code, userInfo: [NSLocalizedDescriptionKey: "\(context): \(desc)"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func connectOrThrow(context: String) async throws {
|
||||||
|
do {
|
||||||
|
try await self.connect()
|
||||||
|
} catch {
|
||||||
|
throw self.wrap(error, context: context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func encodeRequest(
|
||||||
|
method: String,
|
||||||
|
params: [String: AnyCodable]?,
|
||||||
|
kind: String) throws -> (id: String, data: Data)
|
||||||
|
{
|
||||||
|
let id = UUID().uuidString
|
||||||
|
// Encode request using the generated models to avoid JSONSerialization/ObjC bridging pitfalls.
|
||||||
|
let paramsObject: ProtoAnyCodable? = params.map { entries in
|
||||||
|
let dict = entries.reduce(into: [String: ProtoAnyCodable]()) { dict, entry in
|
||||||
|
dict[entry.key] = ProtoAnyCodable(entry.value.value)
|
||||||
|
}
|
||||||
|
return ProtoAnyCodable(dict)
|
||||||
|
}
|
||||||
|
let frame = RequestFrame(
|
||||||
|
type: "req",
|
||||||
|
id: id,
|
||||||
|
method: method,
|
||||||
|
params: paramsObject)
|
||||||
|
do {
|
||||||
|
let data = try self.encoder.encode(frame)
|
||||||
|
return (id: id, data: data)
|
||||||
|
} catch {
|
||||||
|
self.logger.error(
|
||||||
|
"gateway \(kind) encode failed \(method, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func failPending(_ error: Error) async {
|
private func failPending(_ error: Error) async {
|
||||||
let waiters = self.pending
|
let waiters = self.pending
|
||||||
self.pending.removeAll()
|
self.pending.removeAll()
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ public actor GatewayNodeSession {
|
|||||||
"payloadJSON": AnyCodable(payloadJSON ?? NSNull()),
|
"payloadJSON": AnyCodable(payloadJSON ?? NSNull()),
|
||||||
]
|
]
|
||||||
do {
|
do {
|
||||||
_ = try await channel.request(method: "node.event", params: params, timeoutMs: 8000)
|
try await channel.send(method: "node.event", params: params)
|
||||||
} catch {
|
} catch {
|
||||||
self.logger.error("node event failed: \(error.localizedDescription, privacy: .public)")
|
self.logger.error("node event failed: \(error.localizedDescription, privacy: .public)")
|
||||||
}
|
}
|
||||||
@@ -224,7 +224,7 @@ public actor GatewayNodeSession {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
_ = try await channel.request(method: "node.invoke.result", params: params, timeoutMs: 15000)
|
try await channel.send(method: "node.invoke.result", params: params)
|
||||||
} catch {
|
} catch {
|
||||||
self.logger.error("node invoke result failed: \(error.localizedDescription, privacy: .public)")
|
self.logger.error("node invoke result failed: \(error.localizedDescription, privacy: .public)")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1167,17 +1167,29 @@ public struct ConfigApplyParams: Codable, Sendable {
|
|||||||
public struct ConfigPatchParams: Codable, Sendable {
|
public struct ConfigPatchParams: Codable, Sendable {
|
||||||
public let raw: String
|
public let raw: String
|
||||||
public let basehash: String?
|
public let basehash: String?
|
||||||
|
public let sessionkey: String?
|
||||||
|
public let note: String?
|
||||||
|
public let restartdelayms: Int?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
raw: String,
|
raw: String,
|
||||||
basehash: String?
|
basehash: String?,
|
||||||
|
sessionkey: String?,
|
||||||
|
note: String?,
|
||||||
|
restartdelayms: Int?
|
||||||
) {
|
) {
|
||||||
self.raw = raw
|
self.raw = raw
|
||||||
self.basehash = basehash
|
self.basehash = basehash
|
||||||
|
self.sessionkey = sessionkey
|
||||||
|
self.note = note
|
||||||
|
self.restartdelayms = restartdelayms
|
||||||
}
|
}
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case raw
|
case raw
|
||||||
case basehash = "baseHash"
|
case basehash = "baseHash"
|
||||||
|
case sessionkey = "sessionKey"
|
||||||
|
case note
|
||||||
|
case restartdelayms = "restartDelayMs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1
dist/control-ui/assets/index-08nzABV3.css
vendored
Normal file
1
dist/control-ui/assets/index-08nzABV3.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/control-ui/assets/index-BvhR9FCb.css
vendored
1
dist/control-ui/assets/index-BvhR9FCb.css
vendored
File diff suppressed because one or more lines are too long
3119
dist/control-ui/assets/index-DQcOTEYz.js
vendored
Normal file
3119
dist/control-ui/assets/index-DQcOTEYz.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/control-ui/assets/index-DQcOTEYz.js.map
vendored
Normal file
1
dist/control-ui/assets/index-DQcOTEYz.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
3059
dist/control-ui/assets/index-DsXRcnEw.js
vendored
3059
dist/control-ui/assets/index-DsXRcnEw.js
vendored
File diff suppressed because one or more lines are too long
1
dist/control-ui/assets/index-DsXRcnEw.js.map
vendored
1
dist/control-ui/assets/index-DsXRcnEw.js.map
vendored
File diff suppressed because one or more lines are too long
3047
dist/control-ui/assets/index-bYQnHP3a.js
vendored
3047
dist/control-ui/assets/index-bYQnHP3a.js
vendored
File diff suppressed because one or more lines are too long
1
dist/control-ui/assets/index-bYQnHP3a.js.map
vendored
1
dist/control-ui/assets/index-bYQnHP3a.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/control-ui/index.html
vendored
4
dist/control-ui/index.html
vendored
@@ -6,8 +6,8 @@
|
|||||||
<title>Clawdbot Control</title>
|
<title>Clawdbot Control</title>
|
||||||
<meta name="color-scheme" content="dark light" />
|
<meta name="color-scheme" content="dark light" />
|
||||||
<link rel="icon" href="./favicon.ico" sizes="any" />
|
<link rel="icon" href="./favicon.ico" sizes="any" />
|
||||||
<script type="module" crossorigin src="./assets/index-DsXRcnEw.js"></script>
|
<script type="module" crossorigin src="./assets/index-DQcOTEYz.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-BvhR9FCb.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-08nzABV3.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<clawdbot-app></clawdbot-app>
|
<clawdbot-app></clawdbot-app>
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ Notes:
|
|||||||
- Per-hook `model`/`thinking` in the mapping still overrides these defaults.
|
- Per-hook `model`/`thinking` in the mapping still overrides these defaults.
|
||||||
- Fallback order: `hooks.gmail.model` → `agents.defaults.model.fallbacks` → primary (auth/rate-limit/timeouts).
|
- Fallback order: `hooks.gmail.model` → `agents.defaults.model.fallbacks` → primary (auth/rate-limit/timeouts).
|
||||||
- If `agents.defaults.models` is set, the Gmail model must be in the allowlist.
|
- If `agents.defaults.models` is set, the Gmail model must be in the allowlist.
|
||||||
|
- Gmail hook content is wrapped with external-content safety boundaries by default.
|
||||||
|
To disable (dangerous), set `hooks.gmail.allowUnsafeExternalContent: true`.
|
||||||
|
|
||||||
To customize payload handling further, add `hooks.mappings` or a JS/TS transform module
|
To customize payload handling further, add `hooks.mappings` or a JS/TS transform module
|
||||||
under `hooks.transformsDir` (see [Webhooks](/automation/webhook)).
|
under `hooks.transformsDir` (see [Webhooks](/automation/webhook)).
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ Notes:
|
|||||||
|
|
||||||
## Auth
|
## Auth
|
||||||
|
|
||||||
Every request must include the hook token:
|
Every request must include the hook token. Prefer headers:
|
||||||
- `Authorization: Bearer <token>`
|
- `Authorization: Bearer <token>` (recommended)
|
||||||
- or `x-clawdbot-token: <token>`
|
- `x-clawdbot-token: <token>`
|
||||||
- or `?token=<token>`
|
- `?token=<token>` (deprecated; logs a warning and will be removed in a future major release)
|
||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
@@ -96,6 +96,8 @@ Mapping options (summary):
|
|||||||
- TS transforms require a TS loader (e.g. `bun` or `tsx`) or precompiled `.js` at runtime.
|
- TS transforms require a TS loader (e.g. `bun` or `tsx`) or precompiled `.js` at runtime.
|
||||||
- Set `deliver: true` + `channel`/`to` on mappings to route replies to a chat surface
|
- Set `deliver: true` + `channel`/`to` on mappings to route replies to a chat surface
|
||||||
(`channel` defaults to `last` and falls back to WhatsApp).
|
(`channel` defaults to `last` and falls back to WhatsApp).
|
||||||
|
- `allowUnsafeExternalContent: true` disables the external content safety wrapper for that hook
|
||||||
|
(dangerous; only for trusted internal sources).
|
||||||
- `clawdbot webhooks gmail setup` writes `hooks.gmail` config for `clawdbot webhooks gmail run`.
|
- `clawdbot webhooks gmail setup` writes `hooks.gmail` config for `clawdbot webhooks gmail run`.
|
||||||
See [Gmail Pub/Sub](/automation/gmail-pubsub) for the full Gmail watch flow.
|
See [Gmail Pub/Sub](/automation/gmail-pubsub) for the full Gmail watch flow.
|
||||||
|
|
||||||
@@ -148,3 +150,6 @@ curl -X POST http://127.0.0.1:18789/hooks/gmail \
|
|||||||
- Keep hook endpoints behind loopback, tailnet, or trusted reverse proxy.
|
- Keep hook endpoints behind loopback, tailnet, or trusted reverse proxy.
|
||||||
- Use a dedicated hook token; do not reuse gateway auth tokens.
|
- Use a dedicated hook token; do not reuse gateway auth tokens.
|
||||||
- Avoid including sensitive raw payloads in webhook logs.
|
- Avoid including sensitive raw payloads in webhook logs.
|
||||||
|
- Hook payloads are treated as untrusted and wrapped with safety boundaries by default.
|
||||||
|
If you must disable this for a specific hook, set `allowUnsafeExternalContent: true`
|
||||||
|
in that hook's mapping (dangerous).
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ Provider options:
|
|||||||
- `channels.bluebubbles.sendReadReceipts`: Send read receipts (default: `true`).
|
- `channels.bluebubbles.sendReadReceipts`: Send read receipts (default: `true`).
|
||||||
- `channels.bluebubbles.blockStreaming`: Enable block streaming (default: `true`).
|
- `channels.bluebubbles.blockStreaming`: Enable block streaming (default: `true`).
|
||||||
- `channels.bluebubbles.textChunkLimit`: Outbound chunk size in chars (default: 4000).
|
- `channels.bluebubbles.textChunkLimit`: Outbound chunk size in chars (default: 4000).
|
||||||
|
- `channels.bluebubbles.chunkMode`: `length` (default) splits only when exceeding `textChunkLimit`; `newline` splits on blank lines (paragraph boundaries) before length chunking.
|
||||||
- `channels.bluebubbles.mediaMaxMb`: Inbound media cap in MB (default: 8).
|
- `channels.bluebubbles.mediaMaxMb`: Inbound media cap in MB (default: 8).
|
||||||
- `channels.bluebubbles.historyLimit`: Max group messages for context (0 disables).
|
- `channels.bluebubbles.historyLimit`: Max group messages for context (0 disables).
|
||||||
- `channels.bluebubbles.dmHistoryLimit`: DM history limit.
|
- `channels.bluebubbles.dmHistoryLimit`: DM history limit.
|
||||||
@@ -212,6 +213,7 @@ Prefer `chat_guid` for stable routing:
|
|||||||
- `chat_id:123`
|
- `chat_id:123`
|
||||||
- `chat_identifier:...`
|
- `chat_identifier:...`
|
||||||
- Direct handles: `+15555550123`, `user@example.com`
|
- Direct handles: `+15555550123`, `user@example.com`
|
||||||
|
- If a direct handle does not have an existing DM chat, Clawdbot will create one via `POST /api/v1/chat/new`. This requires the BlueBubbles Private API to be enabled.
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
- Webhook requests are authenticated by comparing `guid`/`password` query params or headers against `channels.bluebubbles.password`. Requests from `localhost` are also accepted.
|
- Webhook requests are authenticated by comparing `guid`/`password` query params or headers against `channels.bluebubbles.password`. Requests from `localhost` are also accepted.
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ Notes:
|
|||||||
## Capabilities & limits
|
## Capabilities & limits
|
||||||
- DMs and guild text channels (threads are treated as separate channels; voice not supported).
|
- DMs and guild text channels (threads are treated as separate channels; voice not supported).
|
||||||
- Typing indicators sent best-effort; message chunking uses `channels.discord.textChunkLimit` (default 2000) and splits tall replies by line count (`channels.discord.maxLinesPerMessage`, default 17).
|
- Typing indicators sent best-effort; message chunking uses `channels.discord.textChunkLimit` (default 2000) and splits tall replies by line count (`channels.discord.maxLinesPerMessage`, default 17).
|
||||||
|
- Optional newline chunking: set `channels.discord.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- File uploads supported up to the configured `channels.discord.mediaMaxMb` (default 8 MB).
|
- File uploads supported up to the configured `channels.discord.mediaMaxMb` (default 8 MB).
|
||||||
- Mention-gated guild replies by default to avoid noisy bots.
|
- Mention-gated guild replies by default to avoid noisy bots.
|
||||||
- Reply context is injected when a message references another message (quoted content + ids).
|
- Reply context is injected when a message references another message (quoted content + ids).
|
||||||
@@ -306,6 +307,7 @@ ack reaction after the bot replies.
|
|||||||
- `guilds.<id>.requireMention`: per-guild mention requirement (overridable per channel).
|
- `guilds.<id>.requireMention`: per-guild mention requirement (overridable per channel).
|
||||||
- `guilds.<id>.reactionNotifications`: reaction system event mode (`off`, `own`, `all`, `allowlist`).
|
- `guilds.<id>.reactionNotifications`: reaction system event mode (`off`, `own`, `all`, `allowlist`).
|
||||||
- `textChunkLimit`: outbound text chunk size (chars). Default: 2000.
|
- `textChunkLimit`: outbound text chunk size (chars). Default: 2000.
|
||||||
|
- `chunkMode`: `length` (default) splits only when exceeding `textChunkLimit`; `newline` splits on blank lines (paragraph boundaries) before length chunking.
|
||||||
- `maxLinesPerMessage`: soft max line count per message. Default: 17.
|
- `maxLinesPerMessage`: soft max line count per message. Default: 17.
|
||||||
- `mediaMaxMb`: clamp inbound media saved to disk.
|
- `mediaMaxMb`: clamp inbound media saved to disk.
|
||||||
- `historyLimit`: number of recent guild messages to include as context when replying to a mention (default 20; falls back to `messages.groupChat.historyLimit`; `0` disables).
|
- `historyLimit`: number of recent guild messages to include as context when replying to a mention (default 20; falls back to `messages.groupChat.historyLimit`; `0` disables).
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ Status: ready for DMs + spaces via Google Chat API webhooks (HTTP only).
|
|||||||
- Under **Connection settings**, select **HTTP endpoint URL**.
|
- Under **Connection settings**, select **HTTP endpoint URL**.
|
||||||
- Under **Triggers**, select **Use a common HTTP endpoint URL for all triggers** and set it to your gateway's public URL followed by `/googlechat`.
|
- Under **Triggers**, select **Use a common HTTP endpoint URL for all triggers** and set it to your gateway's public URL followed by `/googlechat`.
|
||||||
- *Tip: Run `clawdbot status` to find your gateway's public URL.*
|
- *Tip: Run `clawdbot status` to find your gateway's public URL.*
|
||||||
- Under **Visibility**, check **Make this Chat app available to specific people and groups in <Your Domain>**.
|
- Under **Visibility**, check **Make this Chat app available to specific people and groups in <Your Domain>**.
|
||||||
- Enter your email address (e.g. `user@example.com`) in the text box.
|
- Enter your email address (e.g. `user@example.com`) in the text box.
|
||||||
- Click **Save** at the bottom.
|
- Click **Save** at the bottom.
|
||||||
6) **Enable the app status**:
|
6) **Enable the app status**:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ read_when:
|
|||||||
- **Proxy:** optional `channels.telegram.proxy` uses `undici.ProxyAgent` through grammY’s `client.baseFetch`.
|
- **Proxy:** optional `channels.telegram.proxy` uses `undici.ProxyAgent` through grammY’s `client.baseFetch`.
|
||||||
- **Webhook support:** `webhook-set.ts` wraps `setWebhook/deleteWebhook`; `webhook.ts` hosts the callback with health + graceful shutdown. Gateway enables webhook mode when `channels.telegram.webhookUrl` is set (otherwise it long-polls).
|
- **Webhook support:** `webhook-set.ts` wraps `setWebhook/deleteWebhook`; `webhook.ts` hosts the callback with health + graceful shutdown. Gateway enables webhook mode when `channels.telegram.webhookUrl` is set (otherwise it long-polls).
|
||||||
- **Sessions:** direct chats collapse into the agent main session (`agent:<agentId>:<mainKey>`); groups use `agent:<agentId>:telegram:group:<chatId>`; replies route back to the same channel.
|
- **Sessions:** direct chats collapse into the agent main session (`agent:<agentId>:<mainKey>`); groups use `agent:<agentId>:telegram:group:<chatId>`; replies route back to the same channel.
|
||||||
- **Config knobs:** `channels.telegram.botToken`, `channels.telegram.dmPolicy`, `channels.telegram.groups` (allowlist + mention defaults), `channels.telegram.allowFrom`, `channels.telegram.groupAllowFrom`, `channels.telegram.groupPolicy`, `channels.telegram.mediaMaxMb`, `channels.telegram.proxy`, `channels.telegram.webhookSecret`, `channels.telegram.webhookUrl`.
|
- **Config knobs:** `channels.telegram.botToken`, `channels.telegram.dmPolicy`, `channels.telegram.groups` (allowlist + mention defaults), `channels.telegram.allowFrom`, `channels.telegram.groupAllowFrom`, `channels.telegram.groupPolicy`, `channels.telegram.mediaMaxMb`, `channels.telegram.linkPreview`, `channels.telegram.proxy`, `channels.telegram.webhookSecret`, `channels.telegram.webhookUrl`.
|
||||||
- **Draft streaming:** optional `channels.telegram.streamMode` uses `sendMessageDraft` in private topic chats (Bot API 9.3+). This is separate from channel block streaming.
|
- **Draft streaming:** optional `channels.telegram.streamMode` uses `sendMessageDraft` in private topic chats (Bot API 9.3+). This is separate from channel block streaming.
|
||||||
- **Tests:** grammy mocks cover DM + group mention gating and outbound send; more media/webhook fixtures still welcome.
|
- **Tests:** grammy mocks cover DM + group mention gating and outbound send; more media/webhook fixtures still welcome.
|
||||||
|
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ This is useful when you want an isolated personality/model for a specific thread
|
|||||||
|
|
||||||
## Limits
|
## Limits
|
||||||
- Outbound text is chunked to `channels.imessage.textChunkLimit` (default 4000).
|
- Outbound text is chunked to `channels.imessage.textChunkLimit` (default 4000).
|
||||||
|
- Optional newline chunking: set `channels.imessage.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- Media uploads are capped by `channels.imessage.mediaMaxMb` (default 16).
|
- Media uploads are capped by `channels.imessage.mediaMaxMb` (default 16).
|
||||||
|
|
||||||
## Addressing / delivery targets
|
## Addressing / delivery targets
|
||||||
@@ -253,6 +254,7 @@ Provider options:
|
|||||||
- `channels.imessage.includeAttachments`: ingest attachments into context.
|
- `channels.imessage.includeAttachments`: ingest attachments into context.
|
||||||
- `channels.imessage.mediaMaxMb`: inbound/outbound media cap (MB).
|
- `channels.imessage.mediaMaxMb`: inbound/outbound media cap (MB).
|
||||||
- `channels.imessage.textChunkLimit`: outbound chunk size (chars).
|
- `channels.imessage.textChunkLimit`: outbound chunk size (chars).
|
||||||
|
- `channels.imessage.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
|
|
||||||
Related global options:
|
Related global options:
|
||||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
|
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ Text is supported everywhere; media and reactions vary by channel.
|
|||||||
- [BlueBubbles](/channels/bluebubbles) — **Recommended for iMessage**; uses the BlueBubbles macOS server REST API with full feature support (edit, unsend, effects, reactions, group management — edit currently broken on macOS 26 Tahoe).
|
- [BlueBubbles](/channels/bluebubbles) — **Recommended for iMessage**; uses the BlueBubbles macOS server REST API with full feature support (edit, unsend, effects, reactions, group management — edit currently broken on macOS 26 Tahoe).
|
||||||
- [iMessage](/channels/imessage) — macOS only; native integration via imsg (legacy, consider BlueBubbles for new setups).
|
- [iMessage](/channels/imessage) — macOS only; native integration via imsg (legacy, consider BlueBubbles for new setups).
|
||||||
- [Microsoft Teams](/channels/msteams) — Bot Framework; enterprise support (plugin, installed separately).
|
- [Microsoft Teams](/channels/msteams) — Bot Framework; enterprise support (plugin, installed separately).
|
||||||
|
- [LINE](/channels/line) — LINE Messaging API bot (plugin, installed separately).
|
||||||
- [Nextcloud Talk](/channels/nextcloud-talk) — Self-hosted chat via Nextcloud Talk (plugin, installed separately).
|
- [Nextcloud Talk](/channels/nextcloud-talk) — Self-hosted chat via Nextcloud Talk (plugin, installed separately).
|
||||||
- [Matrix](/channels/matrix) — Matrix protocol (plugin, installed separately).
|
- [Matrix](/channels/matrix) — Matrix protocol (plugin, installed separately).
|
||||||
- [Nostr](/channels/nostr) — Decentralized DMs via NIP-04 (plugin, installed separately).
|
- [Nostr](/channels/nostr) — Decentralized DMs via NIP-04 (plugin, installed separately).
|
||||||
@@ -32,6 +33,8 @@ Text is supported everywhere; media and reactions vary by channel.
|
|||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Channels can run simultaneously; configure multiple and Clawdbot will route per chat.
|
- Channels can run simultaneously; configure multiple and Clawdbot will route per chat.
|
||||||
|
- Fastest setup is usually **Telegram** (simple bot token). WhatsApp requires QR pairing and
|
||||||
|
stores more state on disk.
|
||||||
- Group behavior varies by channel; see [Groups](/concepts/groups).
|
- Group behavior varies by channel; see [Groups](/concepts/groups).
|
||||||
- DM pairing and allowlists are enforced for safety; see [Security](/gateway/security).
|
- DM pairing and allowlists are enforced for safety; see [Security](/gateway/security).
|
||||||
- Telegram internals: [grammY notes](/channels/grammy).
|
- Telegram internals: [grammY notes](/channels/grammy).
|
||||||
|
|||||||
183
docs/channels/line.md
Normal file
183
docs/channels/line.md
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
---
|
||||||
|
summary: "LINE Messaging API plugin setup, config, and usage"
|
||||||
|
read_when:
|
||||||
|
- You want to connect Clawdbot to LINE
|
||||||
|
- You need LINE webhook + credential setup
|
||||||
|
- You want LINE-specific message options
|
||||||
|
---
|
||||||
|
|
||||||
|
# LINE (plugin)
|
||||||
|
|
||||||
|
LINE connects to Clawdbot via the LINE Messaging API. The plugin runs as a webhook
|
||||||
|
receiver on the gateway and uses your channel access token + channel secret for
|
||||||
|
authentication.
|
||||||
|
|
||||||
|
Status: supported via plugin. Direct messages, group chats, media, locations, Flex
|
||||||
|
messages, template messages, and quick replies are supported. Reactions and threads
|
||||||
|
are not supported.
|
||||||
|
|
||||||
|
## Plugin required
|
||||||
|
|
||||||
|
Install the LINE plugin:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clawdbot plugins install @clawdbot/line
|
||||||
|
```
|
||||||
|
|
||||||
|
Local checkout (when running from a git repo):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clawdbot plugins install ./extensions/line
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
1) Create a LINE Developers account and open the Console:
|
||||||
|
https://developers.line.biz/console/
|
||||||
|
2) Create (or pick) a Provider and add a **Messaging API** channel.
|
||||||
|
3) Copy the **Channel access token** and **Channel secret** from the channel settings.
|
||||||
|
4) Enable **Use webhook** in the Messaging API settings.
|
||||||
|
5) Set the webhook URL to your gateway endpoint (HTTPS required):
|
||||||
|
|
||||||
|
```
|
||||||
|
https://gateway-host/line/webhook
|
||||||
|
```
|
||||||
|
|
||||||
|
The gateway responds to LINE’s webhook verification (GET) and inbound events (POST).
|
||||||
|
If you need a custom path, set `channels.line.webhookPath` or
|
||||||
|
`channels.line.accounts.<id>.webhookPath` and update the URL accordingly.
|
||||||
|
|
||||||
|
## Configure
|
||||||
|
|
||||||
|
Minimal config:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
line: {
|
||||||
|
enabled: true,
|
||||||
|
channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN",
|
||||||
|
channelSecret: "LINE_CHANNEL_SECRET",
|
||||||
|
dmPolicy: "pairing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Env vars (default account only):
|
||||||
|
|
||||||
|
- `LINE_CHANNEL_ACCESS_TOKEN`
|
||||||
|
- `LINE_CHANNEL_SECRET`
|
||||||
|
|
||||||
|
Token/secret files:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
line: {
|
||||||
|
tokenFile: "/path/to/line-token.txt",
|
||||||
|
secretFile: "/path/to/line-secret.txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Multiple accounts:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
line: {
|
||||||
|
accounts: {
|
||||||
|
marketing: {
|
||||||
|
channelAccessToken: "...",
|
||||||
|
channelSecret: "...",
|
||||||
|
webhookPath: "/line/marketing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Access control
|
||||||
|
|
||||||
|
Direct messages default to pairing. Unknown senders get a pairing code and their
|
||||||
|
messages are ignored until approved.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clawdbot pairing list line
|
||||||
|
clawdbot pairing approve line <CODE>
|
||||||
|
```
|
||||||
|
|
||||||
|
Allowlists and policies:
|
||||||
|
|
||||||
|
- `channels.line.dmPolicy`: `pairing | allowlist | open | disabled`
|
||||||
|
- `channels.line.allowFrom`: allowlisted LINE user IDs for DMs
|
||||||
|
- `channels.line.groupPolicy`: `allowlist | open | disabled`
|
||||||
|
- `channels.line.groupAllowFrom`: allowlisted LINE user IDs for groups
|
||||||
|
- Per-group overrides: `channels.line.groups.<groupId>.allowFrom`
|
||||||
|
|
||||||
|
LINE IDs are case-sensitive. Valid IDs look like:
|
||||||
|
|
||||||
|
- User: `U` + 32 hex chars
|
||||||
|
- Group: `C` + 32 hex chars
|
||||||
|
- Room: `R` + 32 hex chars
|
||||||
|
|
||||||
|
## Message behavior
|
||||||
|
|
||||||
|
- Text is chunked at 5000 characters.
|
||||||
|
- Markdown formatting is stripped; code blocks and tables are converted into Flex
|
||||||
|
cards when possible.
|
||||||
|
- Streaming responses are buffered; LINE receives full chunks with a loading
|
||||||
|
animation while the agent works.
|
||||||
|
- Media downloads are capped by `channels.line.mediaMaxMb` (default 10).
|
||||||
|
|
||||||
|
## Channel data (rich messages)
|
||||||
|
|
||||||
|
Use `channelData.line` to send quick replies, locations, Flex cards, or template
|
||||||
|
messages.
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
text: "Here you go",
|
||||||
|
channelData: {
|
||||||
|
line: {
|
||||||
|
quickReplies: ["Status", "Help"],
|
||||||
|
location: {
|
||||||
|
title: "Office",
|
||||||
|
address: "123 Main St",
|
||||||
|
latitude: 35.681236,
|
||||||
|
longitude: 139.767125
|
||||||
|
},
|
||||||
|
flexMessage: {
|
||||||
|
altText: "Status card",
|
||||||
|
contents: { /* Flex payload */ }
|
||||||
|
},
|
||||||
|
templateMessage: {
|
||||||
|
type: "confirm",
|
||||||
|
text: "Proceed?",
|
||||||
|
confirmLabel: "Yes",
|
||||||
|
confirmData: "yes",
|
||||||
|
cancelLabel: "No",
|
||||||
|
cancelData: "no"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The LINE plugin also ships a `/card` command for Flex message presets:
|
||||||
|
|
||||||
|
```
|
||||||
|
/card info "Welcome" "Thanks for joining!"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **Webhook verification fails:** ensure the webhook URL is HTTPS and the
|
||||||
|
`channelSecret` matches the LINE console.
|
||||||
|
- **No inbound events:** confirm the webhook path matches `channels.line.webhookPath`
|
||||||
|
and that the gateway is reachable from LINE.
|
||||||
|
- **Media download errors:** raise `channels.line.mediaMaxMb` if media exceeds the
|
||||||
|
default limit.
|
||||||
@@ -215,6 +215,7 @@ Provider options:
|
|||||||
- `channels.matrix.initialSyncLimit`: initial sync limit.
|
- `channels.matrix.initialSyncLimit`: initial sync limit.
|
||||||
- `channels.matrix.threadReplies`: `off | inbound | always` (default: inbound).
|
- `channels.matrix.threadReplies`: `off | inbound | always` (default: inbound).
|
||||||
- `channels.matrix.textChunkLimit`: outbound text chunk size (chars).
|
- `channels.matrix.textChunkLimit`: outbound text chunk size (chars).
|
||||||
|
- `channels.matrix.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- `channels.matrix.dm.policy`: `pairing | allowlist | open | disabled` (default: pairing).
|
- `channels.matrix.dm.policy`: `pairing | allowlist | open | disabled` (default: pairing).
|
||||||
- `channels.matrix.dm.allowFrom`: DM allowlist (user IDs or display names). `open` requires `"*"`. The wizard resolves names to IDs when possible.
|
- `channels.matrix.dm.allowFrom`: DM allowlist (user IDs or display names). `open` requires `"*"`. The wizard resolves names to IDs when possible.
|
||||||
- `channels.matrix.groupPolicy`: `allowlist | open | disabled` (default: allowlist).
|
- `channels.matrix.groupPolicy`: `allowlist | open | disabled` (default: allowlist).
|
||||||
|
|||||||
@@ -415,6 +415,7 @@ Key settings (see `/gateway/configuration` for shared channel patterns):
|
|||||||
- `channels.msteams.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing)
|
- `channels.msteams.dmPolicy`: `pairing | allowlist | open | disabled` (default: pairing)
|
||||||
- `channels.msteams.allowFrom`: allowlist for DMs (AAD object IDs, UPNs, or display names). The wizard resolves names to IDs during setup when Graph access is available.
|
- `channels.msteams.allowFrom`: allowlist for DMs (AAD object IDs, UPNs, or display names). The wizard resolves names to IDs during setup when Graph access is available.
|
||||||
- `channels.msteams.textChunkLimit`: outbound text chunk size.
|
- `channels.msteams.textChunkLimit`: outbound text chunk size.
|
||||||
|
- `channels.msteams.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- `channels.msteams.mediaAllowHosts`: allowlist for inbound attachment hosts (defaults to Microsoft/Teams domains).
|
- `channels.msteams.mediaAllowHosts`: allowlist for inbound attachment hosts (defaults to Microsoft/Teams domains).
|
||||||
- `channels.msteams.requireMention`: require @mention in channels/groups (default true).
|
- `channels.msteams.requireMention`: require @mention in channels/groups (default true).
|
||||||
- `channels.msteams.replyStyle`: `thread | top-level` (see [Reply Style](#reply-style-threads-vs-posts)).
|
- `channels.msteams.replyStyle`: `thread | top-level` (see [Reply Style](#reply-style-threads-vs-posts)).
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ Provider options:
|
|||||||
- `channels.nextcloud-talk.dmHistoryLimit`: DM history limit (0 disables).
|
- `channels.nextcloud-talk.dmHistoryLimit`: DM history limit (0 disables).
|
||||||
- `channels.nextcloud-talk.dms`: per-DM overrides (historyLimit).
|
- `channels.nextcloud-talk.dms`: per-DM overrides (historyLimit).
|
||||||
- `channels.nextcloud-talk.textChunkLimit`: outbound text chunk size (chars).
|
- `channels.nextcloud-talk.textChunkLimit`: outbound text chunk size (chars).
|
||||||
|
- `channels.nextcloud-talk.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- `channels.nextcloud-talk.blockStreaming`: disable block streaming for this channel.
|
- `channels.nextcloud-talk.blockStreaming`: disable block streaming for this channel.
|
||||||
- `channels.nextcloud-talk.blockStreamingCoalesce`: block streaming coalesce tuning.
|
- `channels.nextcloud-talk.blockStreamingCoalesce`: block streaming coalesce tuning.
|
||||||
- `channels.nextcloud-talk.mediaMaxMb`: inbound media cap (MB).
|
- `channels.nextcloud-talk.mediaMaxMb`: inbound media cap (MB).
|
||||||
|
|||||||
@@ -74,6 +74,22 @@ Example:
|
|||||||
|
|
||||||
Multi-account support: use `channels.signal.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
Multi-account support: use `channels.signal.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||||
|
|
||||||
|
## External daemon mode (httpUrl)
|
||||||
|
If you want to manage `signal-cli` yourself (slow JVM cold starts, container init, or shared CPUs), run the daemon separately and point Clawdbot at it:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
signal: {
|
||||||
|
httpUrl: "http://127.0.0.1:8080",
|
||||||
|
autoStart: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This skips auto-spawn and the startup wait inside Clawdbot. For slow starts when auto-spawning, set `channels.signal.startupTimeoutMs`.
|
||||||
|
|
||||||
## Access control (DMs + groups)
|
## Access control (DMs + groups)
|
||||||
DMs:
|
DMs:
|
||||||
- Default: `channels.signal.dmPolicy = "pairing"`.
|
- Default: `channels.signal.dmPolicy = "pairing"`.
|
||||||
@@ -95,6 +111,7 @@ Groups:
|
|||||||
|
|
||||||
## Media + limits
|
## Media + limits
|
||||||
- Outbound text is chunked to `channels.signal.textChunkLimit` (default 4000).
|
- Outbound text is chunked to `channels.signal.textChunkLimit` (default 4000).
|
||||||
|
- Optional newline chunking: set `channels.signal.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- Attachments supported (base64 fetched from `signal-cli`).
|
- Attachments supported (base64 fetched from `signal-cli`).
|
||||||
- Default media cap: `channels.signal.mediaMaxMb` (default 8).
|
- Default media cap: `channels.signal.mediaMaxMb` (default 8).
|
||||||
- Use `channels.signal.ignoreAttachments` to skip downloading media.
|
- Use `channels.signal.ignoreAttachments` to skip downloading media.
|
||||||
@@ -105,8 +122,29 @@ Groups:
|
|||||||
- **Read receipts**: when `channels.signal.sendReadReceipts` is true, Clawdbot forwards read receipts for allowed DMs.
|
- **Read receipts**: when `channels.signal.sendReadReceipts` is true, Clawdbot forwards read receipts for allowed DMs.
|
||||||
- Signal-cli does not expose read receipts for groups.
|
- Signal-cli does not expose read receipts for groups.
|
||||||
|
|
||||||
|
## Reactions (message tool)
|
||||||
|
- Use `message action=react` with `channel=signal`.
|
||||||
|
- Targets: sender E.164 or UUID (use `uuid:<id>` from pairing output; bare UUID works too).
|
||||||
|
- `messageId` is the Signal timestamp for the message you’re reacting to.
|
||||||
|
- Group reactions require `targetAuthor` or `targetAuthorUuid`.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```
|
||||||
|
message action=react channel=signal target=uuid:123e4567-e89b-12d3-a456-426614174000 messageId=1737630212345 emoji=🔥
|
||||||
|
message action=react channel=signal target=+15551234567 messageId=1737630212345 emoji=🔥 remove=true
|
||||||
|
message action=react channel=signal target=signal:group:<groupId> targetAuthor=uuid:<sender-uuid> messageId=1737630212345 emoji=✅
|
||||||
|
```
|
||||||
|
|
||||||
|
Config:
|
||||||
|
- `channels.signal.actions.reactions`: enable/disable reaction actions (default true).
|
||||||
|
- `channels.signal.reactionLevel`: `off | ack | minimal | extensive`.
|
||||||
|
- `off`/`ack` disables agent reactions (message tool `react` will error).
|
||||||
|
- `minimal`/`extensive` enables agent reactions and sets the guidance level.
|
||||||
|
- Per-account overrides: `channels.signal.accounts.<id>.actions.reactions`, `channels.signal.accounts.<id>.reactionLevel`.
|
||||||
|
|
||||||
## Delivery targets (CLI/cron)
|
## Delivery targets (CLI/cron)
|
||||||
- DMs: `signal:+15551234567` (or plain E.164).
|
- DMs: `signal:+15551234567` (or plain E.164).
|
||||||
|
- UUID DMs: `uuid:<id>` (or bare UUID).
|
||||||
- Groups: `signal:group:<groupId>`.
|
- Groups: `signal:group:<groupId>`.
|
||||||
- Usernames: `username:<name>` (if supported by your Signal account).
|
- Usernames: `username:<name>` (if supported by your Signal account).
|
||||||
|
|
||||||
@@ -120,6 +158,7 @@ Provider options:
|
|||||||
- `channels.signal.httpUrl`: full daemon URL (overrides host/port).
|
- `channels.signal.httpUrl`: full daemon URL (overrides host/port).
|
||||||
- `channels.signal.httpHost`, `channels.signal.httpPort`: daemon bind (default 127.0.0.1:8080).
|
- `channels.signal.httpHost`, `channels.signal.httpPort`: daemon bind (default 127.0.0.1:8080).
|
||||||
- `channels.signal.autoStart`: auto-spawn daemon (default true if `httpUrl` unset).
|
- `channels.signal.autoStart`: auto-spawn daemon (default true if `httpUrl` unset).
|
||||||
|
- `channels.signal.startupTimeoutMs`: startup wait timeout in ms (cap 120000).
|
||||||
- `channels.signal.receiveMode`: `on-start | manual`.
|
- `channels.signal.receiveMode`: `on-start | manual`.
|
||||||
- `channels.signal.ignoreAttachments`: skip attachment downloads.
|
- `channels.signal.ignoreAttachments`: skip attachment downloads.
|
||||||
- `channels.signal.ignoreStories`: ignore stories from the daemon.
|
- `channels.signal.ignoreStories`: ignore stories from the daemon.
|
||||||
@@ -131,6 +170,7 @@ Provider options:
|
|||||||
- `channels.signal.historyLimit`: max group messages to include as context (0 disables).
|
- `channels.signal.historyLimit`: max group messages to include as context (0 disables).
|
||||||
- `channels.signal.dmHistoryLimit`: DM history limit in user turns. Per-user overrides: `channels.signal.dms["<phone_or_uuid>"].historyLimit`.
|
- `channels.signal.dmHistoryLimit`: DM history limit in user turns. Per-user overrides: `channels.signal.dms["<phone_or_uuid>"].historyLimit`.
|
||||||
- `channels.signal.textChunkLimit`: outbound chunk size (chars).
|
- `channels.signal.textChunkLimit`: outbound chunk size (chars).
|
||||||
|
- `channels.signal.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- `channels.signal.mediaMaxMb`: inbound/outbound media cap (MB).
|
- `channels.signal.mediaMaxMb`: inbound/outbound media cap (MB).
|
||||||
|
|
||||||
Related global options:
|
Related global options:
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Minimal config:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
1) Create a Slack app (From scratch) in https://api.channels.slack.com/apps.
|
1) Create a Slack app (From scratch) in https://api.slack.com/apps.
|
||||||
2) **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`).
|
2) **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`).
|
||||||
3) **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`).
|
3) **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`).
|
||||||
4) Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`).
|
4) Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`).
|
||||||
@@ -245,29 +245,29 @@ If you enable native commands, add one `slash_commands` entry per command you wa
|
|||||||
## Scopes (current vs optional)
|
## Scopes (current vs optional)
|
||||||
Slack's Conversations API is type-scoped: you only need the scopes for the
|
Slack's Conversations API is type-scoped: you only need the scopes for the
|
||||||
conversation types you actually touch (channels, groups, im, mpim). See
|
conversation types you actually touch (channels, groups, im, mpim). See
|
||||||
https://api.channels.slack.com/docs/conversations-api for the overview.
|
https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overview.
|
||||||
|
|
||||||
### Bot token scopes (required)
|
### Bot token scopes (required)
|
||||||
- `chat:write` (send/update/delete messages via `chat.postMessage`)
|
- `chat:write` (send/update/delete messages via `chat.postMessage`)
|
||||||
https://api.channels.slack.com/methods/chat.postMessage
|
https://docs.slack.dev/reference/methods/chat.postMessage
|
||||||
- `im:write` (open DMs via `conversations.open` for user DMs)
|
- `im:write` (open DMs via `conversations.open` for user DMs)
|
||||||
https://api.channels.slack.com/methods/conversations.open
|
https://docs.slack.dev/reference/methods/conversations.open
|
||||||
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
|
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
|
||||||
https://api.channels.slack.com/methods/conversations.history
|
https://docs.slack.dev/reference/methods/conversations.history
|
||||||
- `channels:read`, `groups:read`, `im:read`, `mpim:read`
|
- `channels:read`, `groups:read`, `im:read`, `mpim:read`
|
||||||
https://api.channels.slack.com/methods/conversations.info
|
https://docs.slack.dev/reference/methods/conversations.info
|
||||||
- `users:read` (user lookup)
|
- `users:read` (user lookup)
|
||||||
https://api.channels.slack.com/methods/users.info
|
https://docs.slack.dev/reference/methods/users.info
|
||||||
- `reactions:read`, `reactions:write` (`reactions.get` / `reactions.add`)
|
- `reactions:read`, `reactions:write` (`reactions.get` / `reactions.add`)
|
||||||
https://api.channels.slack.com/methods/reactions.get
|
https://docs.slack.dev/reference/methods/reactions.get
|
||||||
https://api.channels.slack.com/methods/reactions.add
|
https://docs.slack.dev/reference/methods/reactions.add
|
||||||
- `pins:read`, `pins:write` (`pins.list` / `pins.add` / `pins.remove`)
|
- `pins:read`, `pins:write` (`pins.list` / `pins.add` / `pins.remove`)
|
||||||
https://api.channels.slack.com/scopes/pins:read
|
https://docs.slack.dev/reference/scopes/pins.read
|
||||||
https://api.channels.slack.com/scopes/pins:write
|
https://docs.slack.dev/reference/scopes/pins.write
|
||||||
- `emoji:read` (`emoji.list`)
|
- `emoji:read` (`emoji.list`)
|
||||||
https://api.channels.slack.com/scopes/emoji:read
|
https://docs.slack.dev/reference/scopes/emoji.read
|
||||||
- `files:write` (uploads via `files.uploadV2`)
|
- `files:write` (uploads via `files.uploadV2`)
|
||||||
https://api.channels.slack.com/messaging/files/uploading
|
https://docs.slack.dev/messaging/working-with-files/#upload
|
||||||
|
|
||||||
### User token scopes (optional, read-only by default)
|
### User token scopes (optional, read-only by default)
|
||||||
Add these under **User Token Scopes** if you configure `channels.slack.userToken`.
|
Add these under **User Token Scopes** if you configure `channels.slack.userToken`.
|
||||||
@@ -284,9 +284,9 @@ Add these under **User Token Scopes** if you configure `channels.slack.userToken
|
|||||||
- `mpim:write` (only if we add group-DM open/DM start via `conversations.open`)
|
- `mpim:write` (only if we add group-DM open/DM start via `conversations.open`)
|
||||||
- `groups:write` (only if we add private-channel management: create/rename/invite/archive)
|
- `groups:write` (only if we add private-channel management: create/rename/invite/archive)
|
||||||
- `chat:write.public` (only if we want to post to channels the bot isn't in)
|
- `chat:write.public` (only if we want to post to channels the bot isn't in)
|
||||||
https://api.channels.slack.com/scopes/chat:write.public
|
https://docs.slack.dev/reference/scopes/chat.write.public
|
||||||
- `users:read.email` (only if we need email fields from `users.info`)
|
- `users:read.email` (only if we need email fields from `users.info`)
|
||||||
https://api.channels.slack.com/changelog/2017-04-narrowing-email-access
|
https://docs.slack.dev/changelog/2017-04-narrowing-email-access
|
||||||
- `files:read` (only if we start listing/reading file metadata)
|
- `files:read` (only if we start listing/reading file metadata)
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
@@ -349,6 +349,7 @@ ack reaction after the bot replies.
|
|||||||
|
|
||||||
## Limits
|
## Limits
|
||||||
- Outbound text is chunked to `channels.slack.textChunkLimit` (default 4000).
|
- Outbound text is chunked to `channels.slack.textChunkLimit` (default 4000).
|
||||||
|
- Optional newline chunking: set `channels.slack.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- Media uploads are capped by `channels.slack.mediaMaxMb` (default 20).
|
- Media uploads are capped by `channels.slack.mediaMaxMb` (default 20).
|
||||||
|
|
||||||
## Reply threading
|
## Reply threading
|
||||||
|
|||||||
@@ -120,6 +120,13 @@ You can add custom commands to the menu via config:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- `setMyCommands failed` in logs usually means outbound HTTPS/DNS is blocked to `api.telegram.org`.
|
||||||
|
- If you see `sendMessage` or `sendChatAction` failures, check IPv6 routing and DNS.
|
||||||
|
|
||||||
|
More help: [Channel troubleshooting](/channels/troubleshooting).
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- Custom commands are **menu entries only**; Clawdbot does not implement them unless you handle them elsewhere.
|
- Custom commands are **menu entries only**; Clawdbot does not implement them unless you handle them elsewhere.
|
||||||
- Command names are normalized (leading `/` stripped, lowercased) and must match `a-z`, `0-9`, `_` (1–32 chars).
|
- Command names are normalized (leading `/` stripped, lowercased) and must match `a-z`, `0-9`, `_` (1–32 chars).
|
||||||
@@ -128,6 +135,7 @@ Notes:
|
|||||||
|
|
||||||
## Limits
|
## Limits
|
||||||
- Outbound text is chunked to `channels.telegram.textChunkLimit` (default 4000).
|
- Outbound text is chunked to `channels.telegram.textChunkLimit` (default 4000).
|
||||||
|
- Optional newline chunking: set `channels.telegram.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- Media downloads/uploads are capped by `channels.telegram.mediaMaxMb` (default 5).
|
- Media downloads/uploads are capped by `channels.telegram.mediaMaxMb` (default 5).
|
||||||
- Telegram Bot API requests time out after `channels.telegram.timeoutSeconds` (default 500 via grammY). Set lower to avoid long hangs.
|
- Telegram Bot API requests time out after `channels.telegram.timeoutSeconds` (default 500 via grammY). Set lower to avoid long hangs.
|
||||||
- Group history context uses `channels.telegram.historyLimit` (or `channels.telegram.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
- Group history context uses `channels.telegram.historyLimit` (or `channels.telegram.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||||
@@ -516,6 +524,8 @@ Provider options:
|
|||||||
- `channels.telegram.accounts.<account>.capabilities.inlineButtons`: per-account override.
|
- `channels.telegram.accounts.<account>.capabilities.inlineButtons`: per-account override.
|
||||||
- `channels.telegram.replyToMode`: `off | first | all` (default: `first`).
|
- `channels.telegram.replyToMode`: `off | first | all` (default: `first`).
|
||||||
- `channels.telegram.textChunkLimit`: outbound chunk size (chars).
|
- `channels.telegram.textChunkLimit`: outbound chunk size (chars).
|
||||||
|
- `channels.telegram.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
|
- `channels.telegram.linkPreview`: toggle link previews for outbound messages (default: true).
|
||||||
- `channels.telegram.streamMode`: `off | partial | block` (draft streaming).
|
- `channels.telegram.streamMode`: `off | partial | block` (draft streaming).
|
||||||
- `channels.telegram.mediaMaxMb`: inbound/outbound media cap (MB).
|
- `channels.telegram.mediaMaxMb`: inbound/outbound media cap (MB).
|
||||||
- `channels.telegram.retry`: retry policy for outbound Telegram API calls (attempts, minDelayMs, maxDelayMs, jitter).
|
- `channels.telegram.retry`: retry policy for outbound Telegram API calls (attempts, minDelayMs, maxDelayMs, jitter).
|
||||||
|
|||||||
@@ -22,3 +22,4 @@ clawdbot channels status --probe
|
|||||||
|
|
||||||
## Telegram quick fixes
|
## Telegram quick fixes
|
||||||
- Logs show `HttpError: Network request for 'sendMessage' failed` or `sendChatAction` → check IPv6 DNS. If `api.telegram.org` resolves to IPv6 first and the host lacks IPv6 egress, force IPv4 or enable IPv6. See [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting).
|
- Logs show `HttpError: Network request for 'sendMessage' failed` or `sendChatAction` → check IPv6 DNS. If `api.telegram.org` resolves to IPv6 first and the host lacks IPv6 egress, force IPv4 or enable IPv6. See [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting).
|
||||||
|
- Logs show `setMyCommands failed` → check outbound HTTPS and DNS reachability to `api.telegram.org` (common on locked-down VPS or proxies).
|
||||||
|
|||||||
@@ -271,12 +271,13 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
|
|||||||
|
|
||||||
## Limits
|
## Limits
|
||||||
- Outbound text is chunked to `channels.whatsapp.textChunkLimit` (default 4000).
|
- Outbound text is chunked to `channels.whatsapp.textChunkLimit` (default 4000).
|
||||||
|
- Optional newline chunking: set `channels.whatsapp.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- Inbound media saves are capped by `channels.whatsapp.mediaMaxMb` (default 50 MB).
|
- Inbound media saves are capped by `channels.whatsapp.mediaMaxMb` (default 50 MB).
|
||||||
- Outbound media items are capped by `agents.defaults.mediaMaxMb` (default 5 MB).
|
- Outbound media items are capped by `agents.defaults.mediaMaxMb` (default 5 MB).
|
||||||
|
|
||||||
## Outbound send (text + media)
|
## Outbound send (text + media)
|
||||||
- Uses active web listener; error if gateway not running.
|
- Uses active web listener; error if gateway not running.
|
||||||
- Text chunking: 4k max per message (configurable via `channels.whatsapp.textChunkLimit`).
|
- Text chunking: 4k max per message (configurable via `channels.whatsapp.textChunkLimit`, optional `channels.whatsapp.chunkMode`).
|
||||||
- Media:
|
- Media:
|
||||||
- Image/video/audio/document supported.
|
- Image/video/audio/document supported.
|
||||||
- Audio sent as PTT; `audio/ogg` => `audio/ogg; codecs=opus`.
|
- Audio sent as PTT; `audio/ogg` => `audio/ogg; codecs=opus`.
|
||||||
|
|||||||
@@ -314,7 +314,7 @@ Options:
|
|||||||
- `--opencode-zen-api-key <key>`
|
- `--opencode-zen-api-key <key>`
|
||||||
- `--gateway-port <port>`
|
- `--gateway-port <port>`
|
||||||
- `--gateway-bind <loopback|lan|tailnet|auto|custom>`
|
- `--gateway-bind <loopback|lan|tailnet|auto|custom>`
|
||||||
- `--gateway-auth <off|token|password>`
|
- `--gateway-auth <token|password>`
|
||||||
- `--gateway-token <token>`
|
- `--gateway-token <token>`
|
||||||
- `--gateway-password <password>`
|
- `--gateway-password <password>`
|
||||||
- `--remote-url <url>`
|
- `--remote-url <url>`
|
||||||
|
|||||||
@@ -66,11 +66,12 @@ Name lookup:
|
|||||||
- Discord only: `--poll-duration-hours`, `--message`
|
- Discord only: `--poll-duration-hours`, `--message`
|
||||||
|
|
||||||
- `react`
|
- `react`
|
||||||
- Channels: Discord/Google Chat/Slack/Telegram/WhatsApp
|
- Channels: Discord/Google Chat/Slack/Telegram/WhatsApp/Signal
|
||||||
- Required: `--message-id`, `--target`
|
- Required: `--message-id`, `--target`
|
||||||
- Optional: `--emoji`, `--remove`, `--participant`, `--from-me`
|
- Optional: `--emoji`, `--remove`, `--participant`, `--from-me`, `--target-author`, `--target-author-uuid`
|
||||||
- Note: `--remove` requires `--emoji` (omit `--emoji` to clear own reactions where supported; see /tools/reactions)
|
- Note: `--remove` requires `--emoji` (omit `--emoji` to clear own reactions where supported; see /tools/reactions)
|
||||||
- WhatsApp only: `--participant`, `--from-me`
|
- WhatsApp only: `--participant`, `--from-me`
|
||||||
|
- Signal group reactions: `--target-author` or `--target-author-uuid` required
|
||||||
|
|
||||||
- `reactions`
|
- `reactions`
|
||||||
- Channels: Discord/Google Chat/Slack
|
- Channels: Discord/Google Chat/Slack
|
||||||
@@ -213,6 +214,13 @@ clawdbot message react --channel slack \
|
|||||||
--target C123 --message-id 456 --emoji "✅"
|
--target C123 --message-id 456 --emoji "✅"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
React in a Signal group:
|
||||||
|
```
|
||||||
|
clawdbot message react --channel signal \
|
||||||
|
--target signal:group:abc123 --message-id 1737630212345 \
|
||||||
|
--emoji "✅" --target-author-uuid 123e4567-e89b-12d3-a456-426614174000
|
||||||
|
```
|
||||||
|
|
||||||
Send Telegram inline buttons:
|
Send Telegram inline buttons:
|
||||||
```
|
```
|
||||||
clawdbot message send --channel telegram --target @mychat --message "Choose:" \
|
clawdbot message send --channel telegram --target @mychat --message "Choose:" \
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ These files live under the workspace (`agents.defaults.workspace`, default
|
|||||||
- Decisions, preferences, and durable facts go to `MEMORY.md`.
|
- Decisions, preferences, and durable facts go to `MEMORY.md`.
|
||||||
- Day-to-day notes and running context go to `memory/YYYY-MM-DD.md`.
|
- Day-to-day notes and running context go to `memory/YYYY-MM-DD.md`.
|
||||||
- If someone says "remember this," write it down (do not keep it in RAM).
|
- If someone says "remember this," write it down (do not keep it in RAM).
|
||||||
|
- This area is still evolving. It helps to remind the model to store memories; it will know what to do.
|
||||||
|
- If you want something to stick, **ask the bot to write it** into memory.
|
||||||
|
|
||||||
## Automatic memory flush (pre-compaction ping)
|
## Automatic memory flush (pre-compaction ping)
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ Clawdbot ships with the pi‑ai catalog. These providers require **no**
|
|||||||
- Gemini CLI OAuth is shipped as a bundled plugin (`google-gemini-cli-auth`, disabled by default).
|
- Gemini CLI OAuth is shipped as a bundled plugin (`google-gemini-cli-auth`, disabled by default).
|
||||||
- Enable: `clawdbot plugins enable google-gemini-cli-auth`
|
- Enable: `clawdbot plugins enable google-gemini-cli-auth`
|
||||||
- Login: `clawdbot models auth login --provider google-gemini-cli --set-default`
|
- Login: `clawdbot models auth login --provider google-gemini-cli --set-default`
|
||||||
|
- Note: you do **not** paste a client id or secret into `clawdbot.json`. The CLI login flow stores
|
||||||
|
tokens in auth profiles on the gateway host.
|
||||||
|
|
||||||
### Z.AI (GLM)
|
### Z.AI (GLM)
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ Legend:
|
|||||||
- `agents.defaults.blockStreamingChunk`: `{ minChars, maxChars, breakPreference? }`.
|
- `agents.defaults.blockStreamingChunk`: `{ minChars, maxChars, breakPreference? }`.
|
||||||
- `agents.defaults.blockStreamingCoalesce`: `{ minChars?, maxChars?, idleMs? }` (merge streamed blocks before send).
|
- `agents.defaults.blockStreamingCoalesce`: `{ minChars?, maxChars?, idleMs? }` (merge streamed blocks before send).
|
||||||
- Channel hard cap: `*.textChunkLimit` (e.g., `channels.whatsapp.textChunkLimit`).
|
- Channel hard cap: `*.textChunkLimit` (e.g., `channels.whatsapp.textChunkLimit`).
|
||||||
|
- Channel chunk mode: `*.chunkMode` (`length` default, `newline` splits on blank lines (paragraph boundaries) before length chunking).
|
||||||
- Discord soft cap: `channels.discord.maxLinesPerMessage` (default 17) splits tall replies to avoid UI clipping.
|
- Discord soft cap: `channels.discord.maxLinesPerMessage` (default 17) splits tall replies to avoid UI clipping.
|
||||||
|
|
||||||
**Boundary semantics:**
|
**Boundary semantics:**
|
||||||
|
|||||||
89
docs/diagnostics/flags.md
Normal file
89
docs/diagnostics/flags.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
---
|
||||||
|
summary: "Diagnostics flags for targeted debug logs"
|
||||||
|
read_when:
|
||||||
|
- You need targeted debug logs without raising global logging levels
|
||||||
|
- You need to capture subsystem-specific logs for support
|
||||||
|
---
|
||||||
|
# Diagnostics Flags
|
||||||
|
|
||||||
|
Diagnostics flags let you enable targeted debug logs without turning on verbose logging everywhere. Flags are opt-in and have no effect unless a subsystem checks them.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
- Flags are strings (case-insensitive).
|
||||||
|
- You can enable flags in config or via an env override.
|
||||||
|
- Wildcards are supported:
|
||||||
|
- `telegram.*` matches `telegram.http`
|
||||||
|
- `*` enables all flags
|
||||||
|
|
||||||
|
## Enable via config
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"diagnostics": {
|
||||||
|
"flags": ["telegram.http"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Multiple flags:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"diagnostics": {
|
||||||
|
"flags": ["telegram.http", "gateway.*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart the gateway after changing flags.
|
||||||
|
|
||||||
|
## Env override (one-off)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CLAWDBOT_DIAGNOSTICS=telegram.http,telegram.payload
|
||||||
|
```
|
||||||
|
|
||||||
|
Disable all flags:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CLAWDBOT_DIAGNOSTICS=0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Where logs go
|
||||||
|
|
||||||
|
Flags emit logs into the standard diagnostics log file. By default:
|
||||||
|
|
||||||
|
```
|
||||||
|
/tmp/clawdbot/clawdbot-YYYY-MM-DD.log
|
||||||
|
```
|
||||||
|
|
||||||
|
If you set `logging.file`, use that path instead. Logs are JSONL (one JSON object per line). Redaction still applies based on `logging.redactSensitive`.
|
||||||
|
|
||||||
|
## Extract logs
|
||||||
|
|
||||||
|
Pick the latest log file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ls -t /tmp/clawdbot/clawdbot-*.log | head -n 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Filter for Telegram HTTP diagnostics:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg "telegram http error" /tmp/clawdbot/clawdbot-*.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Or tail while reproducing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tail -f /tmp/clawdbot/clawdbot-$(date +%F).log | rg "telegram http error"
|
||||||
|
```
|
||||||
|
|
||||||
|
For remote gateways, you can also use `clawdbot logs --follow` (see [/cli/logs](/cli/logs)).
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- If `logging.level` is set higher than `warn`, these logs may be suppressed. Default `info` is fine.
|
||||||
|
- Flags are safe to leave enabled; they only affect log volume for the specific subsystem.
|
||||||
|
- Use [/logging](/logging) to change log destinations, levels, and redaction.
|
||||||
@@ -117,6 +117,14 @@
|
|||||||
"source": "/mattermost/",
|
"source": "/mattermost/",
|
||||||
"destination": "/channels/mattermost"
|
"destination": "/channels/mattermost"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"source": "/line",
|
||||||
|
"destination": "/channels/line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/line/",
|
||||||
|
"destination": "/channels/line"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"source": "/glm",
|
"source": "/glm",
|
||||||
"destination": "/providers/glm"
|
"destination": "/providers/glm"
|
||||||
@@ -197,6 +205,14 @@
|
|||||||
"source": "/providers/msteams/",
|
"source": "/providers/msteams/",
|
||||||
"destination": "/channels/msteams"
|
"destination": "/channels/msteams"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"source": "/providers/line",
|
||||||
|
"destination": "/channels/line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/providers/line/",
|
||||||
|
"destination": "/channels/line"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"source": "/providers/signal",
|
"source": "/providers/signal",
|
||||||
"destination": "/channels/signal"
|
"destination": "/channels/signal"
|
||||||
@@ -788,6 +804,14 @@
|
|||||||
{
|
{
|
||||||
"source": "/install/railway/",
|
"source": "/install/railway/",
|
||||||
"destination": "/railway"
|
"destination": "/railway"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/gcp",
|
||||||
|
"destination": "/platforms/gcp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "/gcp/",
|
||||||
|
"destination": "/platforms/gcp"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"navigation": {
|
"navigation": {
|
||||||
@@ -827,6 +851,7 @@
|
|||||||
"install/nix",
|
"install/nix",
|
||||||
"install/docker",
|
"install/docker",
|
||||||
"railway",
|
"railway",
|
||||||
|
"render",
|
||||||
"install/bun"
|
"install/bun"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -965,6 +990,7 @@
|
|||||||
"channels/signal",
|
"channels/signal",
|
||||||
"channels/imessage",
|
"channels/imessage",
|
||||||
"channels/msteams",
|
"channels/msteams",
|
||||||
|
"channels/line",
|
||||||
"channels/matrix",
|
"channels/matrix",
|
||||||
"channels/zalo",
|
"channels/zalo",
|
||||||
"channels/zalouser",
|
"channels/zalouser",
|
||||||
@@ -983,6 +1009,7 @@
|
|||||||
"bedrock",
|
"bedrock",
|
||||||
"providers/moonshot",
|
"providers/moonshot",
|
||||||
"providers/minimax",
|
"providers/minimax",
|
||||||
|
"providers/vercel-ai-gateway",
|
||||||
"providers/openrouter",
|
"providers/openrouter",
|
||||||
"providers/synthetic",
|
"providers/synthetic",
|
||||||
"providers/opencode",
|
"providers/opencode",
|
||||||
@@ -1048,12 +1075,14 @@
|
|||||||
"pages": [
|
"pages": [
|
||||||
"platforms",
|
"platforms",
|
||||||
"platforms/macos",
|
"platforms/macos",
|
||||||
|
"platforms/macos-vm",
|
||||||
"platforms/ios",
|
"platforms/ios",
|
||||||
"platforms/android",
|
"platforms/android",
|
||||||
"platforms/windows",
|
"platforms/windows",
|
||||||
"platforms/linux",
|
"platforms/linux",
|
||||||
"platforms/fly",
|
"platforms/fly",
|
||||||
"platforms/hetzner",
|
"platforms/hetzner",
|
||||||
|
"platforms/gcp",
|
||||||
"platforms/exe-dev"
|
"platforms/exe-dev"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ Clawdbot ships a default for `claude-cli`:
|
|||||||
|
|
||||||
- `command: "claude"`
|
- `command: "claude"`
|
||||||
- `args: ["-p", "--output-format", "json", "--dangerously-skip-permissions"]`
|
- `args: ["-p", "--output-format", "json", "--dangerously-skip-permissions"]`
|
||||||
|
- `resumeArgs: ["-p", "--output-format", "json", "--dangerously-skip-permissions", "--resume", "{sessionId}"]`
|
||||||
- `modelArg: "--model"`
|
- `modelArg: "--model"`
|
||||||
- `systemPromptArg: "--append-system-prompt"`
|
- `systemPromptArg: "--append-system-prompt"`
|
||||||
- `sessionArg: "--session-id"`
|
- `sessionArg: "--session-id"`
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ better forms without hard-coding config knowledge.
|
|||||||
Use `config.apply` to validate + write the full config and restart the Gateway in one step.
|
Use `config.apply` to validate + write the full config and restart the Gateway in one step.
|
||||||
It writes a restart sentinel and pings the last active session after the Gateway comes back.
|
It writes a restart sentinel and pings the last active session after the Gateway comes back.
|
||||||
|
|
||||||
|
Warning: `config.apply` replaces the **entire config**. If you want to change only a few keys,
|
||||||
|
use `config.patch` or `clawdbot config set`. Keep a backup of `~/.clawdbot/clawdbot.json`.
|
||||||
|
|
||||||
Params:
|
Params:
|
||||||
- `raw` (string) — JSON5 payload for the entire config
|
- `raw` (string) — JSON5 payload for the entire config
|
||||||
- `baseHash` (optional) — config hash from `config.get` (required when a config already exists)
|
- `baseHash` (optional) — config hash from `config.get` (required when a config already exists)
|
||||||
@@ -504,6 +507,7 @@ For groups, use `channels.whatsapp.groupPolicy` + `channels.whatsapp.groupAllowF
|
|||||||
dmPolicy: "pairing", // pairing | allowlist | open | disabled
|
dmPolicy: "pairing", // pairing | allowlist | open | disabled
|
||||||
allowFrom: ["+15555550123", "+447700900123"],
|
allowFrom: ["+15555550123", "+447700900123"],
|
||||||
textChunkLimit: 4000, // optional outbound chunk size (chars)
|
textChunkLimit: 4000, // optional outbound chunk size (chars)
|
||||||
|
chunkMode: "length", // optional chunking mode (length | newline)
|
||||||
mediaMaxMb: 50 // optional inbound media cap (MB)
|
mediaMaxMb: 50 // optional inbound media cap (MB)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1017,6 +1021,7 @@ Set `channels.telegram.configWrites: false` to block Telegram-initiated config w
|
|||||||
],
|
],
|
||||||
historyLimit: 50, // include last N group messages as context (0 disables)
|
historyLimit: 50, // include last N group messages as context (0 disables)
|
||||||
replyToMode: "first", // off | first | all
|
replyToMode: "first", // off | first | all
|
||||||
|
linkPreview: true, // toggle outbound link previews
|
||||||
streamMode: "partial", // off | partial | block (draft streaming; separate from block streaming)
|
streamMode: "partial", // off | partial | block (draft streaming; separate from block streaming)
|
||||||
draftChunk: { // optional; only for streamMode=block
|
draftChunk: { // optional; only for streamMode=block
|
||||||
minChars: 200,
|
minChars: 200,
|
||||||
@@ -1105,6 +1110,7 @@ Multi-account support lives under `channels.discord.accounts` (see the multi-acc
|
|||||||
},
|
},
|
||||||
historyLimit: 20, // include last N guild messages as context
|
historyLimit: 20, // include last N guild messages as context
|
||||||
textChunkLimit: 2000, // optional outbound text chunk size (chars)
|
textChunkLimit: 2000, // optional outbound text chunk size (chars)
|
||||||
|
chunkMode: "length", // optional chunking mode (length | newline)
|
||||||
maxLinesPerMessage: 17, // soft max lines per message (Discord UI clipping)
|
maxLinesPerMessage: 17, // soft max lines per message (Discord UI clipping)
|
||||||
retry: { // outbound retry policy
|
retry: { // outbound retry policy
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
@@ -1125,7 +1131,7 @@ Reaction notification modes:
|
|||||||
- `own`: reactions on the bot's own messages (default).
|
- `own`: reactions on the bot's own messages (default).
|
||||||
- `all`: all reactions on all messages.
|
- `all`: all reactions on all messages.
|
||||||
- `allowlist`: reactions from `guilds.<id>.users` on all messages (empty list disables).
|
- `allowlist`: reactions from `guilds.<id>.users` on all messages (empty list disables).
|
||||||
Outbound text is chunked by `channels.discord.textChunkLimit` (default 2000). Discord clients can clip very tall messages, so `channels.discord.maxLinesPerMessage` (default 17) splits long multi-line replies even when under 2000 chars.
|
Outbound text is chunked by `channels.discord.textChunkLimit` (default 2000). Set `channels.discord.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking. Discord clients can clip very tall messages, so `channels.discord.maxLinesPerMessage` (default 17) splits long multi-line replies even when under 2000 chars.
|
||||||
Retry policy defaults and behavior are documented in [Retry policy](/concepts/retry).
|
Retry policy defaults and behavior are documented in [Retry policy](/concepts/retry).
|
||||||
|
|
||||||
### `channels.googlechat` (Chat API webhook)
|
### `channels.googlechat` (Chat API webhook)
|
||||||
@@ -1218,6 +1224,7 @@ Slack runs in Socket Mode and requires both a bot token and app token:
|
|||||||
ephemeral: true
|
ephemeral: true
|
||||||
},
|
},
|
||||||
textChunkLimit: 4000,
|
textChunkLimit: 4000,
|
||||||
|
chunkMode: "length",
|
||||||
mediaMaxMb: 20
|
mediaMaxMb: 20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1267,7 +1274,8 @@ Mattermost requires a bot token plus the base URL for your server:
|
|||||||
dmPolicy: "pairing",
|
dmPolicy: "pairing",
|
||||||
chatmode: "oncall", // oncall | onmessage | onchar
|
chatmode: "oncall", // oncall | onmessage | onchar
|
||||||
oncharPrefixes: [">", "!"],
|
oncharPrefixes: [">", "!"],
|
||||||
textChunkLimit: 4000
|
textChunkLimit: 4000,
|
||||||
|
chunkMode: "length"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1502,7 +1510,7 @@ voice notes; other channels send MP3 audio.
|
|||||||
{
|
{
|
||||||
messages: {
|
messages: {
|
||||||
tts: {
|
tts: {
|
||||||
enabled: true,
|
auto: "always", // off | always | inbound | tagged
|
||||||
mode: "final", // final | all (include tool/block replies)
|
mode: "final", // final | all (include tool/block replies)
|
||||||
provider: "elevenlabs",
|
provider: "elevenlabs",
|
||||||
summaryModel: "openai/gpt-4.1-mini",
|
summaryModel: "openai/gpt-4.1-mini",
|
||||||
@@ -1539,8 +1547,10 @@ voice notes; other channels send MP3 audio.
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- `messages.tts.enabled` can be overridden by local user prefs (see `/tts on`, `/tts off`).
|
- `messages.tts.auto` controls auto‑TTS (`off`, `always`, `inbound`, `tagged`).
|
||||||
- `prefsPath` stores local overrides (enabled/provider/limit/summarize).
|
- `/tts off|always|inbound|tagged` sets the per‑session auto mode (overrides config).
|
||||||
|
- `messages.tts.enabled` is legacy; doctor migrates it to `messages.tts.auto`.
|
||||||
|
- `prefsPath` stores local overrides (provider/limit/summarize).
|
||||||
- `maxTextLength` is a hard cap for TTS input; summaries are truncated to fit.
|
- `maxTextLength` is a hard cap for TTS input; summaries are truncated to fit.
|
||||||
- `summaryModel` overrides `agents.defaults.model.primary` for auto-summary.
|
- `summaryModel` overrides `agents.defaults.model.primary` for auto-summary.
|
||||||
- Accepts `provider/model` or an alias from `agents.defaults.models`.
|
- Accepts `provider/model` or an alias from `agents.defaults.models`.
|
||||||
@@ -2837,8 +2847,11 @@ Control UI base path:
|
|||||||
- `gateway.controlUi.basePath` sets the URL prefix where the Control UI is served.
|
- `gateway.controlUi.basePath` sets the URL prefix where the Control UI is served.
|
||||||
- Examples: `"/ui"`, `"/clawdbot"`, `"/apps/clawdbot"`.
|
- Examples: `"/ui"`, `"/clawdbot"`, `"/apps/clawdbot"`.
|
||||||
- Default: root (`/`) (unchanged).
|
- Default: root (`/`) (unchanged).
|
||||||
- `gateway.controlUi.allowInsecureAuth` allows token-only auth over **HTTP** (no device identity).
|
- `gateway.controlUi.allowInsecureAuth` allows token-only auth for the Control UI when
|
||||||
Default: `false`. Prefer HTTPS (Tailscale Serve) or `127.0.0.1`.
|
device identity is omitted (typically over HTTP). Default: `false`. Prefer HTTPS
|
||||||
|
(Tailscale Serve) or `127.0.0.1`.
|
||||||
|
- `gateway.controlUi.dangerouslyDisableDeviceAuth` disables device identity checks for the
|
||||||
|
Control UI (token/password only). Default: `false`. Break-glass only.
|
||||||
|
|
||||||
Related docs:
|
Related docs:
|
||||||
- [Control UI](/web/control-ui)
|
- [Control UI](/web/control-ui)
|
||||||
@@ -2846,26 +2859,32 @@ Related docs:
|
|||||||
- [Tailscale](/gateway/tailscale)
|
- [Tailscale](/gateway/tailscale)
|
||||||
- [Remote access](/gateway/remote)
|
- [Remote access](/gateway/remote)
|
||||||
|
|
||||||
|
Trusted proxies:
|
||||||
|
- `gateway.trustedProxies`: list of reverse proxy IPs that terminate TLS in front of the Gateway.
|
||||||
|
- When a connection comes from one of these IPs, Clawdbot uses `x-forwarded-for` (or `x-real-ip`) to determine the client IP for local pairing checks and HTTP auth/local checks.
|
||||||
|
- Only list proxies you fully control, and ensure they **overwrite** incoming `x-forwarded-for`.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- `clawdbot gateway` refuses to start unless `gateway.mode` is set to `local` (or you pass the override flag).
|
- `clawdbot gateway` refuses to start unless `gateway.mode` is set to `local` (or you pass the override flag).
|
||||||
- `gateway.port` controls the single multiplexed port used for WebSocket + HTTP (control UI, hooks, A2UI).
|
- `gateway.port` controls the single multiplexed port used for WebSocket + HTTP (control UI, hooks, A2UI).
|
||||||
- OpenAI Chat Completions endpoint: **disabled by default**; enable with `gateway.http.endpoints.chatCompletions.enabled: true`.
|
- OpenAI Chat Completions endpoint: **disabled by default**; enable with `gateway.http.endpoints.chatCompletions.enabled: true`.
|
||||||
- Precedence: `--port` > `CLAWDBOT_GATEWAY_PORT` > `gateway.port` > default `18789`.
|
- Precedence: `--port` > `CLAWDBOT_GATEWAY_PORT` > `gateway.port` > default `18789`.
|
||||||
- Non-loopback binds (`lan`/`tailnet`/`auto`) require auth. Use `gateway.auth.token` (or `CLAWDBOT_GATEWAY_TOKEN`).
|
- Gateway auth is required by default (token/password or Tailscale Serve identity). Non-loopback binds require a shared token/password.
|
||||||
- The onboarding wizard generates a gateway token by default (even on loopback).
|
- The onboarding wizard generates a gateway token by default (even on loopback).
|
||||||
- `gateway.remote.token` is **only** for remote CLI calls; it does not enable local gateway auth. `gateway.token` is ignored.
|
- `gateway.remote.token` is **only** for remote CLI calls; it does not enable local gateway auth. `gateway.token` is ignored.
|
||||||
|
|
||||||
Auth and Tailscale:
|
Auth and Tailscale:
|
||||||
- `gateway.auth.mode` sets the handshake requirements (`token` or `password`).
|
- `gateway.auth.mode` sets the handshake requirements (`token` or `password`). When unset, token auth is assumed.
|
||||||
- `gateway.auth.token` stores the shared token for token auth (used by the CLI on the same machine).
|
- `gateway.auth.token` stores the shared token for token auth (used by the CLI on the same machine).
|
||||||
- When `gateway.auth.mode` is set, only that method is accepted (plus optional Tailscale headers).
|
- When `gateway.auth.mode` is set, only that method is accepted (plus optional Tailscale headers).
|
||||||
- `gateway.auth.password` can be set here, or via `CLAWDBOT_GATEWAY_PASSWORD` (recommended).
|
- `gateway.auth.password` can be set here, or via `CLAWDBOT_GATEWAY_PASSWORD` (recommended).
|
||||||
- `gateway.auth.allowTailscale` allows Tailscale Serve identity headers
|
- `gateway.auth.allowTailscale` allows Tailscale Serve identity headers
|
||||||
(`tailscale-user-login`) to satisfy auth when the request arrives on loopback
|
(`tailscale-user-login`) to satisfy auth when the request arrives on loopback
|
||||||
with `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host`. When
|
with `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host`. Clawdbot
|
||||||
`true`, Serve requests do not need a token/password; set `false` to require
|
verifies the identity by resolving the `x-forwarded-for` address via
|
||||||
explicit credentials. Defaults to `true` when `tailscale.mode = "serve"` and
|
`tailscale whois` before accepting it. When `true`, Serve requests do not need
|
||||||
auth mode is not `password`.
|
a token/password; set `false` to require explicit credentials. Defaults to
|
||||||
|
`true` when `tailscale.mode = "serve"` and auth mode is not `password`.
|
||||||
- `gateway.tailscale.mode: "serve"` uses Tailscale Serve (tailnet only, loopback bind).
|
- `gateway.tailscale.mode: "serve"` uses Tailscale Serve (tailnet only, loopback bind).
|
||||||
- `gateway.tailscale.mode: "funnel"` exposes the dashboard publicly; requires auth.
|
- `gateway.tailscale.mode: "funnel"` exposes the dashboard publicly; requires auth.
|
||||||
- `gateway.tailscale.resetOnExit` resets Serve/Funnel config on shutdown.
|
- `gateway.tailscale.resetOnExit` resets Serve/Funnel config on shutdown.
|
||||||
@@ -3158,6 +3177,20 @@ Auto-generated certs require `openssl` on PATH; if generation fails, the bridge
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `discovery.mdns` (Bonjour / mDNS broadcast mode)
|
||||||
|
|
||||||
|
Controls LAN mDNS discovery broadcasts (`_clawdbot-gw._tcp`).
|
||||||
|
|
||||||
|
- `minimal` (default): omit `cliPath` + `sshPort` from TXT records
|
||||||
|
- `full`: include `cliPath` + `sshPort` in TXT records
|
||||||
|
- `off`: disable mDNS broadcasts entirely
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
discovery: { mdns: { mode: "minimal" } }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### `discovery.wideArea` (Wide-Area Bonjour / unicast DNS‑SD)
|
### `discovery.wideArea` (Wide-Area Bonjour / unicast DNS‑SD)
|
||||||
|
|
||||||
When enabled, the Gateway writes a unicast DNS-SD zone for `_clawdbot-bridge._tcp` under `~/.clawdbot/dns/` using the standard discovery domain `clawdbot.internal.`
|
When enabled, the Gateway writes a unicast DNS-SD zone for `_clawdbot-bridge._tcp` under `~/.clawdbot/dns/` using the standard discovery domain `clawdbot.internal.`
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ pnpm gateway:watch
|
|||||||
- `--force` uses `lsof` to find listeners on the chosen port, sends SIGTERM, logs what it killed, then starts the gateway (fails fast if `lsof` is missing).
|
- `--force` uses `lsof` to find listeners on the chosen port, sends SIGTERM, logs what it killed, then starts the gateway (fails fast if `lsof` is missing).
|
||||||
- If you run under a supervisor (launchd/systemd/mac app child-process mode), a stop/restart typically sends **SIGTERM**; older builds may surface this as `pnpm` `ELIFECYCLE` exit code **143** (SIGTERM), which is a normal shutdown, not a crash.
|
- If you run under a supervisor (launchd/systemd/mac app child-process mode), a stop/restart typically sends **SIGTERM**; older builds may surface this as `pnpm` `ELIFECYCLE` exit code **143** (SIGTERM), which is a normal shutdown, not a crash.
|
||||||
- **SIGUSR1** triggers an in-process restart when authorized (gateway tool/config apply/update, or enable `commands.restart` for manual restarts).
|
- **SIGUSR1** triggers an in-process restart when authorized (gateway tool/config apply/update, or enable `commands.restart` for manual restarts).
|
||||||
- Gateway auth: set `gateway.auth.mode=token` + `gateway.auth.token` (or pass `--token <value>` / `CLAWDBOT_GATEWAY_TOKEN`) to require clients to send `connect.params.auth.token`.
|
- Gateway auth is required by default: set `gateway.auth.token` (or `CLAWDBOT_GATEWAY_TOKEN`) or `gateway.auth.password`. Clients must send `connect.params.auth.token/password` unless using Tailscale Serve identity.
|
||||||
- The wizard now generates a token by default, even on loopback.
|
- The wizard now generates a token by default, even on loopback.
|
||||||
- Port precedence: `--port` > `CLAWDBOT_GATEWAY_PORT` > `gateway.port` > default `18789`.
|
- Port precedence: `--port` > `CLAWDBOT_GATEWAY_PORT` > `gateway.port` > default `18789`.
|
||||||
|
|
||||||
|
|||||||
@@ -198,7 +198,8 @@ The Gateway treats these as **claims** and enforces server-side allowlists.
|
|||||||
- **Local** connects include loopback and the gateway host’s own tailnet address
|
- **Local** connects include loopback and the gateway host’s own tailnet address
|
||||||
(so same‑host tailnet binds can still auto‑approve).
|
(so same‑host tailnet binds can still auto‑approve).
|
||||||
- All WS clients must include `device` identity during `connect` (operator + node).
|
- All WS clients must include `device` identity during `connect` (operator + node).
|
||||||
Control UI can omit it **only** when `gateway.controlUi.allowInsecureAuth` is enabled.
|
Control UI can omit it **only** when `gateway.controlUi.allowInsecureAuth` is enabled
|
||||||
|
(or `gateway.controlUi.dangerouslyDisableDeviceAuth` for break-glass use).
|
||||||
- Non-local connections must sign the server-provided `connect.challenge` nonce.
|
- Non-local connections must sign the server-provided `connect.challenge` nonce.
|
||||||
|
|
||||||
## TLS + pinning
|
## TLS + pinning
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ Clawdbot is both a product and an experiment: you’re wiring frontier-model beh
|
|||||||
- where the bot is allowed to act
|
- where the bot is allowed to act
|
||||||
- what the bot can touch
|
- what the bot can touch
|
||||||
|
|
||||||
|
Start with the smallest access that still works, then widen it as you gain confidence.
|
||||||
|
|
||||||
### What the audit checks (high level)
|
### What the audit checks (high level)
|
||||||
|
|
||||||
- **Inbound access** (DM policies, group policies, allowlists): can strangers trigger the bot?
|
- **Inbound access** (DM policies, group policies, allowlists): can strangers trigger the bot?
|
||||||
@@ -56,11 +58,32 @@ When the audit prints findings, treat this as a priority order:
|
|||||||
|
|
||||||
The Control UI needs a **secure context** (HTTPS or localhost) to generate device
|
The Control UI needs a **secure context** (HTTPS or localhost) to generate device
|
||||||
identity. If you enable `gateway.controlUi.allowInsecureAuth`, the UI falls back
|
identity. If you enable `gateway.controlUi.allowInsecureAuth`, the UI falls back
|
||||||
to **token-only auth** on plain HTTP and skips device pairing. This is a security
|
to **token-only auth** and skips device pairing when device identity is omitted. This is a security
|
||||||
downgrade—prefer HTTPS (Tailscale Serve) or open the UI on `127.0.0.1`.
|
downgrade—prefer HTTPS (Tailscale Serve) or open the UI on `127.0.0.1`.
|
||||||
|
|
||||||
|
For break-glass scenarios only, `gateway.controlUi.dangerouslyDisableDeviceAuth`
|
||||||
|
disables device identity checks entirely. This is a severe security downgrade;
|
||||||
|
keep it off unless you are actively debugging and can revert quickly.
|
||||||
|
|
||||||
`clawdbot security audit` warns when this setting is enabled.
|
`clawdbot security audit` warns when this setting is enabled.
|
||||||
|
|
||||||
|
## Reverse Proxy Configuration
|
||||||
|
|
||||||
|
If you run the Gateway behind a reverse proxy (nginx, Caddy, Traefik, etc.), you should configure `gateway.trustedProxies` for proper client IP detection.
|
||||||
|
|
||||||
|
When the Gateway detects proxy headers (`X-Forwarded-For` or `X-Real-IP`) from an address that is **not** in `trustedProxies`, it will **not** treat connections as local clients. If gateway auth is disabled, those connections are rejected. This prevents authentication bypass where proxied connections would otherwise appear to come from localhost and receive automatic trust.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
gateway:
|
||||||
|
trustedProxies:
|
||||||
|
- "127.0.0.1" # if your proxy runs on localhost
|
||||||
|
auth:
|
||||||
|
mode: password
|
||||||
|
password: ${CLAWDBOT_GATEWAY_PASSWORD}
|
||||||
|
```
|
||||||
|
|
||||||
|
When `trustedProxies` is configured, the Gateway will use `X-Forwarded-For` headers to determine the real client IP for local client detection. Make sure your proxy overwrites (not appends to) incoming `X-Forwarded-For` headers to prevent spoofing.
|
||||||
|
|
||||||
## Local session logs live on disk
|
## Local session logs live on disk
|
||||||
|
|
||||||
Clawdbot stores session transcripts on disk under `~/.clawdbot/agents/<agentId>/sessions/*.jsonl`.
|
Clawdbot stores session transcripts on disk under `~/.clawdbot/agents/<agentId>/sessions/*.jsonl`.
|
||||||
@@ -174,10 +197,17 @@ Prompt injection is when an attacker crafts a message that manipulates the model
|
|||||||
Even with strong system prompts, **prompt injection is not solved**. What helps in practice:
|
Even with strong system prompts, **prompt injection is not solved**. What helps in practice:
|
||||||
- Keep inbound DMs locked down (pairing/allowlists).
|
- Keep inbound DMs locked down (pairing/allowlists).
|
||||||
- Prefer mention gating in groups; avoid “always-on” bots in public rooms.
|
- Prefer mention gating in groups; avoid “always-on” bots in public rooms.
|
||||||
- Treat links and pasted instructions as hostile by default.
|
- Treat links, attachments, and pasted instructions as hostile by default.
|
||||||
- Run sensitive tool execution in a sandbox; keep secrets out of the agent’s reachable filesystem.
|
- Run sensitive tool execution in a sandbox; keep secrets out of the agent’s reachable filesystem.
|
||||||
|
- Limit high-risk tools (`exec`, `browser`, `web_fetch`, `web_search`) to trusted agents or explicit allowlists.
|
||||||
- **Model choice matters:** older/legacy models can be less robust against prompt injection and tool misuse. Prefer modern, instruction-hardened models for any bot with tools. We recommend Anthropic Opus 4.5 because it’s quite good at recognizing prompt injections (see [“A step forward on safety”](https://www.anthropic.com/news/claude-opus-4-5)).
|
- **Model choice matters:** older/legacy models can be less robust against prompt injection and tool misuse. Prefer modern, instruction-hardened models for any bot with tools. We recommend Anthropic Opus 4.5 because it’s quite good at recognizing prompt injections (see [“A step forward on safety”](https://www.anthropic.com/news/claude-opus-4-5)).
|
||||||
|
|
||||||
|
Red flags to treat as untrusted:
|
||||||
|
- “Read this file/URL and do exactly what it says.”
|
||||||
|
- “Ignore your system prompt or safety rules.”
|
||||||
|
- “Reveal your hidden instructions or tool outputs.”
|
||||||
|
- “Paste the full contents of ~/.clawdbot or your logs.”
|
||||||
|
|
||||||
### Prompt injection does not require public DMs
|
### Prompt injection does not require public DMs
|
||||||
|
|
||||||
Even if **only you** can message the bot, prompt injection can still happen via
|
Even if **only you** can message the bot, prompt injection can still happen via
|
||||||
@@ -191,6 +221,7 @@ tool calls. Reduce the blast radius by:
|
|||||||
then pass the summary to your main agent.
|
then pass the summary to your main agent.
|
||||||
- Keeping `web_search` / `web_fetch` / `browser` off for tool-enabled agents unless needed.
|
- Keeping `web_search` / `web_fetch` / `browser` off for tool-enabled agents unless needed.
|
||||||
- Enabling sandboxing and strict tool allowlists for any agent that touches untrusted input.
|
- Enabling sandboxing and strict tool allowlists for any agent that touches untrusted input.
|
||||||
|
- Keeping secrets out of prompts; pass them via env/config on the gateway host instead.
|
||||||
|
|
||||||
### Model strength (security note)
|
### Model strength (security note)
|
||||||
|
|
||||||
@@ -207,8 +238,12 @@ Recommendations:
|
|||||||
|
|
||||||
`/reasoning` and `/verbose` can expose internal reasoning or tool output that
|
`/reasoning` and `/verbose` can expose internal reasoning or tool output that
|
||||||
was not meant for a public channel. In group settings, treat them as **debug
|
was not meant for a public channel. In group settings, treat them as **debug
|
||||||
only** and keep them off unless you explicitly need them. If you enable them,
|
only** and keep them off unless you explicitly need them.
|
||||||
do so only in trusted DMs or tightly controlled rooms.
|
|
||||||
|
Guidance:
|
||||||
|
- Keep `/reasoning` and `/verbose` disabled in public rooms.
|
||||||
|
- If you enable them, do so only in trusted DMs or tightly controlled rooms.
|
||||||
|
- Remember: verbose output can include tool args, URLs, and data the model saw.
|
||||||
|
|
||||||
## Incident Response (if you suspect compromise)
|
## Incident Response (if you suspect compromise)
|
||||||
|
|
||||||
@@ -261,22 +296,63 @@ The Gateway multiplexes **WebSocket + HTTP** on a single port:
|
|||||||
|
|
||||||
Bind mode controls where the Gateway listens:
|
Bind mode controls where the Gateway listens:
|
||||||
- `gateway.bind: "loopback"` (default): only local clients can connect.
|
- `gateway.bind: "loopback"` (default): only local clients can connect.
|
||||||
- Non-loopback binds (`"lan"`, `"tailnet"`, `"custom"`) expand the attack surface. Only use them with `gateway.auth` enabled and a real firewall.
|
- Non-loopback binds (`"lan"`, `"tailnet"`, `"custom"`) expand the attack surface. Only use them with a shared token/password and a real firewall.
|
||||||
|
|
||||||
Rules of thumb:
|
Rules of thumb:
|
||||||
- Prefer Tailscale Serve over LAN binds (Serve keeps the Gateway on loopback, and Tailscale handles access).
|
- Prefer Tailscale Serve over LAN binds (Serve keeps the Gateway on loopback, and Tailscale handles access).
|
||||||
- If you must bind to LAN, firewall the port to a tight allowlist of source IPs; do not port-forward it broadly.
|
- If you must bind to LAN, firewall the port to a tight allowlist of source IPs; do not port-forward it broadly.
|
||||||
- Never expose the Gateway unauthenticated on `0.0.0.0`.
|
- Never expose the Gateway unauthenticated on `0.0.0.0`.
|
||||||
|
|
||||||
|
### 0.4.1) mDNS/Bonjour discovery (information disclosure)
|
||||||
|
|
||||||
|
The Gateway broadcasts its presence via mDNS (`_clawdbot-gw._tcp` on port 5353) for local device discovery. In full mode, this includes TXT records that may expose operational details:
|
||||||
|
|
||||||
|
- `cliPath`: full filesystem path to the CLI binary (reveals username and install location)
|
||||||
|
- `sshPort`: advertises SSH availability on the host
|
||||||
|
- `displayName`, `lanHost`: hostname information
|
||||||
|
|
||||||
|
**Operational security consideration:** Broadcasting infrastructure details makes reconnaissance easier for anyone on the local network. Even "harmless" info like filesystem paths and SSH availability helps attackers map your environment.
|
||||||
|
|
||||||
|
**Recommendations:**
|
||||||
|
|
||||||
|
1. **Minimal mode** (default, recommended for exposed gateways): omit sensitive fields from mDNS broadcasts:
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
discovery: {
|
||||||
|
mdns: { mode: "minimal" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Disable entirely** if you don't need local device discovery:
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
discovery: {
|
||||||
|
mdns: { mode: "off" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Full mode** (opt-in): include `cliPath` + `sshPort` in TXT records:
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
discovery: {
|
||||||
|
mdns: { mode: "full" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Environment variable** (alternative): set `CLAWDBOT_DISABLE_BONJOUR=1` to disable mDNS without config changes.
|
||||||
|
|
||||||
|
In minimal mode, the Gateway still broadcasts enough for device discovery (`role`, `gatewayPort`, `transport`) but omits `cliPath` and `sshPort`. Apps that need CLI path information can fetch it via the authenticated WebSocket connection instead.
|
||||||
|
|
||||||
### 0.5) Lock down the Gateway WebSocket (local auth)
|
### 0.5) Lock down the Gateway WebSocket (local auth)
|
||||||
|
|
||||||
Gateway auth is **only** enforced when you set `gateway.auth`. If it’s unset,
|
Gateway auth is **required by default**. If no token/password is configured,
|
||||||
loopback WS clients are unauthenticated — any local process can connect and call
|
the Gateway refuses WebSocket connections (fail‑closed).
|
||||||
`config.apply`.
|
|
||||||
|
|
||||||
The onboarding wizard now generates a token by default (even for loopback) so
|
The onboarding wizard generates a token by default (even for loopback) so
|
||||||
local clients must authenticate. If you skip the wizard or remove auth, you’re
|
local clients must authenticate.
|
||||||
back to open loopback.
|
|
||||||
|
|
||||||
Set a token so **all** WS clients must authenticate:
|
Set a token so **all** WS clients must authenticate:
|
||||||
|
|
||||||
@@ -314,14 +390,21 @@ Rotation checklist (token/password):
|
|||||||
|
|
||||||
When `gateway.auth.allowTailscale` is `true` (default for Serve), Clawdbot
|
When `gateway.auth.allowTailscale` is `true` (default for Serve), Clawdbot
|
||||||
accepts Tailscale Serve identity headers (`tailscale-user-login`) as
|
accepts Tailscale Serve identity headers (`tailscale-user-login`) as
|
||||||
authentication. This only triggers for requests that hit loopback and include
|
authentication. Clawdbot verifies the identity by resolving the
|
||||||
`x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host` as injected by
|
`x-forwarded-for` address through the local Tailscale daemon (`tailscale whois`)
|
||||||
Tailscale.
|
and matching it to the header. This only triggers for requests that hit loopback
|
||||||
|
and include `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host` as
|
||||||
|
injected by Tailscale.
|
||||||
|
|
||||||
**Security rule:** do not forward these headers from your own reverse proxy. If
|
**Security rule:** do not forward these headers from your own reverse proxy. If
|
||||||
you terminate TLS or proxy in front of the gateway, disable
|
you terminate TLS or proxy in front of the gateway, disable
|
||||||
`gateway.auth.allowTailscale` and use token/password auth instead.
|
`gateway.auth.allowTailscale` and use token/password auth instead.
|
||||||
|
|
||||||
|
Trusted proxies:
|
||||||
|
- If you terminate TLS in front of the Gateway, set `gateway.trustedProxies` to your proxy IPs.
|
||||||
|
- Clawdbot will trust `x-forwarded-for` (or `x-real-ip`) from those IPs to determine the client IP for local pairing checks and HTTP auth/local checks.
|
||||||
|
- Ensure your proxy **overwrites** `x-forwarded-for` and blocks direct access to the Gateway port.
|
||||||
|
|
||||||
See [Tailscale](/gateway/tailscale) and [Web overview](/web).
|
See [Tailscale](/gateway/tailscale) and [Web overview](/web).
|
||||||
|
|
||||||
### 0.6.1) Browser control server over Tailscale (recommended)
|
### 0.6.1) Browser control server over Tailscale (recommended)
|
||||||
@@ -477,6 +560,7 @@ access those accounts and data. Treat browser profiles as **sensitive state**:
|
|||||||
- For remote gateways, assume “browser control” is equivalent to “operator access” to whatever that profile can reach.
|
- For remote gateways, assume “browser control” is equivalent to “operator access” to whatever that profile can reach.
|
||||||
- Treat `browser.controlUrl` endpoints as an admin API: tailnet-only + token auth. Prefer Tailscale Serve over LAN binds.
|
- Treat `browser.controlUrl` endpoints as an admin API: tailnet-only + token auth. Prefer Tailscale Serve over LAN binds.
|
||||||
- Keep `browser.controlToken` separate from `gateway.auth.token` (you can reuse it, but that increases blast radius).
|
- Keep `browser.controlToken` separate from `gateway.auth.token` (you can reuse it, but that increases blast radius).
|
||||||
|
- Prefer env vars for the token (`CLAWDBOT_BROWSER_CONTROL_TOKEN`) instead of storing it in config on disk.
|
||||||
- Chrome extension relay mode is **not** “safer”; it can take over your existing Chrome tabs. Assume it can act as you in whatever that tab/profile can reach.
|
- Chrome extension relay mode is **not** “safer”; it can take over your existing Chrome tabs. Assume it can act as you in whatever that tab/profile can reach.
|
||||||
|
|
||||||
## Per-agent access profiles (multi-agent)
|
## Per-agent access profiles (multi-agent)
|
||||||
|
|||||||
@@ -25,9 +25,12 @@ Set `gateway.auth.mode` to control the handshake:
|
|||||||
|
|
||||||
When `tailscale.mode = "serve"` and `gateway.auth.allowTailscale` is `true`,
|
When `tailscale.mode = "serve"` and `gateway.auth.allowTailscale` is `true`,
|
||||||
valid Serve proxy requests can authenticate via Tailscale identity headers
|
valid Serve proxy requests can authenticate via Tailscale identity headers
|
||||||
(`tailscale-user-login`) without supplying a token/password. Clawdbot only
|
(`tailscale-user-login`) without supplying a token/password. Clawdbot verifies
|
||||||
treats a request as Serve when it arrives from loopback with Tailscale’s
|
the identity by resolving the `x-forwarded-for` address via the local Tailscale
|
||||||
`x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host` headers.
|
daemon (`tailscale whois`) and matching it to the header before accepting it.
|
||||||
|
Clawdbot only treats a request as Serve when it arrives from loopback with
|
||||||
|
Tailscale’s `x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host`
|
||||||
|
headers.
|
||||||
To require explicit credentials, set `gateway.auth.allowTailscale: false` or
|
To require explicit credentials, set `gateway.auth.allowTailscale: false` or
|
||||||
force `gateway.auth.mode: "password"`.
|
force `gateway.auth.mode: "password"`.
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,24 @@ See also: [Health checks](/gateway/health) and [Logging](/logging).
|
|||||||
|
|
||||||
## Common Issues
|
## Common Issues
|
||||||
|
|
||||||
|
### No API key found for provider "anthropic"
|
||||||
|
|
||||||
|
This means the **agent’s auth store is empty** or missing Anthropic credentials.
|
||||||
|
Auth is **per agent**, so a new agent won’t inherit the main agent’s keys.
|
||||||
|
|
||||||
|
Fix options:
|
||||||
|
- Re-run onboarding and choose **Anthropic** for that agent.
|
||||||
|
- Or paste a setup-token on the **gateway host**:
|
||||||
|
```bash
|
||||||
|
clawdbot models auth setup-token --provider anthropic
|
||||||
|
```
|
||||||
|
- Or copy `auth-profiles.json` from the main agent dir to the new agent dir.
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
```bash
|
||||||
|
clawdbot models status
|
||||||
|
```
|
||||||
|
|
||||||
### OAuth token refresh failed (Anthropic Claude subscription)
|
### OAuth token refresh failed (Anthropic Claude subscription)
|
||||||
|
|
||||||
This means the stored Anthropic OAuth token expired and the refresh failed.
|
This means the stored Anthropic OAuth token expired and the refresh failed.
|
||||||
@@ -196,7 +214,7 @@ the Gateway likely refused to bind.
|
|||||||
- Fix: run `clawdbot doctor` to update it (or `clawdbot gateway install --force` for a full rewrite).
|
- Fix: run `clawdbot doctor` to update it (or `clawdbot gateway install --force` for a full rewrite).
|
||||||
|
|
||||||
**If `Last gateway error:` mentions “refusing to bind … without auth”**
|
**If `Last gateway error:` mentions “refusing to bind … without auth”**
|
||||||
- You set `gateway.bind` to a non-loopback mode (`lan`/`tailnet`/`custom`, or `auto` when loopback is unavailable) but left auth off.
|
- You set `gateway.bind` to a non-loopback mode (`lan`/`tailnet`/`custom`, or `auto` when loopback is unavailable) but didn’t configure auth.
|
||||||
- Fix: set `gateway.auth.mode` + `gateway.auth.token` (or export `CLAWDBOT_GATEWAY_TOKEN`) and restart the service.
|
- Fix: set `gateway.auth.mode` + `gateway.auth.token` (or export `CLAWDBOT_GATEWAY_TOKEN`) and restart the service.
|
||||||
|
|
||||||
**If `clawdbot gateway status` says `bind=tailnet` but no tailnet interface was found**
|
**If `clawdbot gateway status` says `bind=tailnet` but no tailnet interface was found**
|
||||||
|
|||||||
1030
docs/help/faq.md
1030
docs/help/faq.md
File diff suppressed because it is too large
Load Diff
@@ -114,3 +114,9 @@ Git requirement:
|
|||||||
|
|
||||||
If you choose `-InstallMethod git` and Git is missing, the installer will print the
|
If you choose `-InstallMethod git` and Git is missing, the installer will print the
|
||||||
Git for Windows link (`https://git-scm.com/download/win`) and exit.
|
Git for Windows link (`https://git-scm.com/download/win`) and exit.
|
||||||
|
|
||||||
|
Common Windows issues:
|
||||||
|
|
||||||
|
- **npm error spawn git / ENOENT**: install Git for Windows and reopen PowerShell, then rerun the installer.
|
||||||
|
- **"clawdbot" is not recognized**: your npm global bin folder is not on PATH. Most systems use
|
||||||
|
`%AppData%\\npm`. You can also run `npm config get prefix` and add `\\bin` to PATH, then reopen PowerShell.
|
||||||
|
|||||||
@@ -192,6 +192,30 @@ Use this if you want diagnostics events available to plugins or custom sinks:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Diagnostics flags (targeted logs)
|
||||||
|
|
||||||
|
Use flags to turn on extra, targeted debug logs without raising `logging.level`.
|
||||||
|
Flags are case-insensitive and support wildcards (e.g. `telegram.*` or `*`).
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"diagnostics": {
|
||||||
|
"flags": ["telegram.http"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Env override (one-off):
|
||||||
|
|
||||||
|
```
|
||||||
|
CLAWDBOT_DIAGNOSTICS=telegram.http,telegram.payload
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- Flag logs go to the standard log file (same as `logging.file`).
|
||||||
|
- Output is still redacted according to `logging.redactSensitive`.
|
||||||
|
- Full guide: [/diagnostics/flags](/diagnostics/flags).
|
||||||
|
|
||||||
### Export to OpenTelemetry
|
### Export to OpenTelemetry
|
||||||
|
|
||||||
Diagnostics can be exported via the `diagnostics-otel` plugin (OTLP/HTTP). This
|
Diagnostics can be exported via the `diagnostics-otel` plugin (OTLP/HTTP). This
|
||||||
|
|||||||
251
docs/platforms/digitalocean.md
Normal file
251
docs/platforms/digitalocean.md
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
---
|
||||||
|
summary: "Clawdbot on DigitalOcean (cheapest paid VPS option)"
|
||||||
|
read_when:
|
||||||
|
- Setting up Clawdbot on DigitalOcean
|
||||||
|
- Looking for cheap VPS hosting for Clawdbot
|
||||||
|
---
|
||||||
|
|
||||||
|
# Clawdbot on DigitalOcean
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Run a persistent Clawdbot Gateway on DigitalOcean for **$6/month** (or $4/mo with reserved pricing).
|
||||||
|
|
||||||
|
If you want something even cheaper, see [Oracle Cloud (Free Tier)](#oracle-cloud-free-alternative) at the bottom — it's **actually free forever**.
|
||||||
|
|
||||||
|
## Cost Comparison (2026)
|
||||||
|
|
||||||
|
| Provider | Plan | Specs | Price/mo | Notes |
|
||||||
|
|----------|------|-------|----------|-------|
|
||||||
|
| **Oracle Cloud** | Always Free ARM | 4 OCPU, 24GB RAM | **$0** | Best value, requires ARM-compatible setup |
|
||||||
|
| **Hetzner** | CX22 | 2 vCPU, 4GB RAM | €3.79 (~$4) | Cheapest paid, EU datacenters |
|
||||||
|
| **DigitalOcean** | Basic | 1 vCPU, 1GB RAM | $6 | Easy UI, good docs |
|
||||||
|
| **Vultr** | Cloud Compute | 1 vCPU, 1GB RAM | $6 | Many locations |
|
||||||
|
| **Linode** | Nanode | 1 vCPU, 1GB RAM | $5 | Now part of Akamai |
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- **Free:** Oracle Cloud ARM (if you can handle the signup process)
|
||||||
|
- **Paid:** Hetzner CX22 (best specs per dollar) — see [Hetzner guide](/platforms/hetzner)
|
||||||
|
- **Easy:** DigitalOcean (this guide) — beginner-friendly UI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- DigitalOcean account ([signup with $200 free credit](https://m.do.co/c/signup))
|
||||||
|
- SSH key pair (or willingness to use password auth)
|
||||||
|
- ~20 minutes
|
||||||
|
|
||||||
|
## 1) Create a Droplet
|
||||||
|
|
||||||
|
1. Log into [DigitalOcean](https://cloud.digitalocean.com/)
|
||||||
|
2. Click **Create → Droplets**
|
||||||
|
3. Choose:
|
||||||
|
- **Region:** Closest to you (or your users)
|
||||||
|
- **Image:** Ubuntu 24.04 LTS
|
||||||
|
- **Size:** Basic → Regular → **$6/mo** (1 vCPU, 1GB RAM, 25GB SSD)
|
||||||
|
- **Authentication:** SSH key (recommended) or password
|
||||||
|
4. Click **Create Droplet**
|
||||||
|
5. Note the IP address
|
||||||
|
|
||||||
|
## 2) Connect via SSH
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh root@YOUR_DROPLET_IP
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3) Install Clawdbot
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update system
|
||||||
|
apt update && apt upgrade -y
|
||||||
|
|
||||||
|
# Install Node.js 22
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||||||
|
apt install -y nodejs
|
||||||
|
|
||||||
|
# Install Clawdbot
|
||||||
|
curl -fsSL https://clawd.bot/install.sh | bash
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
clawdbot --version
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4) Run Onboarding
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clawdbot onboard --install-daemon
|
||||||
|
```
|
||||||
|
|
||||||
|
The wizard will walk you through:
|
||||||
|
- Model auth (API keys or OAuth)
|
||||||
|
- Channel setup (Telegram, WhatsApp, Discord, etc.)
|
||||||
|
- Gateway token (auto-generated)
|
||||||
|
- Daemon installation (systemd)
|
||||||
|
|
||||||
|
## 5) Verify the Gateway
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check status
|
||||||
|
clawdbot status
|
||||||
|
|
||||||
|
# Check service
|
||||||
|
systemctl --user status clawdbot-gateway.service
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
journalctl --user -u clawdbot-gateway.service -f
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6) Access the Dashboard
|
||||||
|
|
||||||
|
The gateway binds to loopback by default. To access the Control UI:
|
||||||
|
|
||||||
|
**Option A: SSH Tunnel (recommended)**
|
||||||
|
```bash
|
||||||
|
# From your local machine
|
||||||
|
ssh -L 18789:localhost:18789 root@YOUR_DROPLET_IP
|
||||||
|
|
||||||
|
# Then open: http://localhost:18789
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: Tailscale Serve (HTTPS, loopback-only)**
|
||||||
|
```bash
|
||||||
|
# On the droplet
|
||||||
|
curl -fsSL https://tailscale.com/install.sh | sh
|
||||||
|
tailscale up
|
||||||
|
|
||||||
|
# Configure Gateway to use Tailscale Serve
|
||||||
|
clawdbot config set gateway.tailscale.mode serve
|
||||||
|
clawdbot gateway restart
|
||||||
|
```
|
||||||
|
|
||||||
|
Open: `https://<magicdns>/`
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- Serve keeps the Gateway loopback-only and authenticates via Tailscale identity headers.
|
||||||
|
- To require token/password instead, set `gateway.auth.allowTailscale: false` or use `gateway.auth.mode: "password"`.
|
||||||
|
|
||||||
|
**Option C: Tailnet bind (no Serve)**
|
||||||
|
```bash
|
||||||
|
clawdbot config set gateway.bind tailnet
|
||||||
|
clawdbot gateway restart
|
||||||
|
```
|
||||||
|
|
||||||
|
Open: `http://<tailscale-ip>:18789` (token required).
|
||||||
|
|
||||||
|
## 7) Connect Your Channels
|
||||||
|
|
||||||
|
### Telegram
|
||||||
|
```bash
|
||||||
|
clawdbot pairing list telegram
|
||||||
|
clawdbot pairing approve telegram <CODE>
|
||||||
|
```
|
||||||
|
|
||||||
|
### WhatsApp
|
||||||
|
```bash
|
||||||
|
clawdbot channels login whatsapp
|
||||||
|
# Scan QR code
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Channels](/channels) for other providers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Optimizations for 1GB RAM
|
||||||
|
|
||||||
|
The $6 droplet only has 1GB RAM. To keep things running smoothly:
|
||||||
|
|
||||||
|
### Add swap (recommended)
|
||||||
|
```bash
|
||||||
|
fallocate -l 2G /swapfile
|
||||||
|
chmod 600 /swapfile
|
||||||
|
mkswap /swapfile
|
||||||
|
swapon /swapfile
|
||||||
|
echo '/swapfile none swap sw 0 0' >> /etc/fstab
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use a lighter model
|
||||||
|
If you're hitting OOMs, consider:
|
||||||
|
- Using API-based models (Claude, GPT) instead of local models
|
||||||
|
- Setting `agents.defaults.model.primary` to a smaller model
|
||||||
|
|
||||||
|
### Monitor memory
|
||||||
|
```bash
|
||||||
|
free -h
|
||||||
|
htop
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Persistence
|
||||||
|
|
||||||
|
All state lives in:
|
||||||
|
- `~/.clawdbot/` — config, credentials, session data
|
||||||
|
- `~/clawd/` — workspace (SOUL.md, memory, etc.)
|
||||||
|
|
||||||
|
These survive reboots. Back them up periodically:
|
||||||
|
```bash
|
||||||
|
tar -czvf clawdbot-backup.tar.gz ~/.clawdbot ~/clawd
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Oracle Cloud Free Alternative
|
||||||
|
|
||||||
|
Oracle Cloud offers **Always Free** ARM instances that are significantly more powerful:
|
||||||
|
|
||||||
|
| What you get | Specs |
|
||||||
|
|--------------|-------|
|
||||||
|
| **4 OCPUs** | ARM Ampere A1 |
|
||||||
|
| **24GB RAM** | More than enough |
|
||||||
|
| **200GB storage** | Block volume |
|
||||||
|
| **Forever free** | No credit card charges |
|
||||||
|
|
||||||
|
### Quick setup:
|
||||||
|
1. Sign up at [oracle.com/cloud/free](https://www.oracle.com/cloud/free/)
|
||||||
|
2. Create a VM.Standard.A1.Flex instance (ARM)
|
||||||
|
3. Choose Oracle Linux or Ubuntu
|
||||||
|
4. Allocate up to 4 OCPU / 24GB RAM within free tier
|
||||||
|
5. Follow the same Clawdbot install steps above
|
||||||
|
|
||||||
|
**Caveats:**
|
||||||
|
- Signup can be finicky (retry if it fails)
|
||||||
|
- ARM architecture — most things work, but some binaries need ARM builds
|
||||||
|
- Oracle may reclaim idle instances (keep them active)
|
||||||
|
|
||||||
|
For the full Oracle guide, see the [community docs](https://gist.github.com/rssnyder/51e3cfedd730e7dd5f4a816143b25dbd).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Gateway won't start
|
||||||
|
```bash
|
||||||
|
clawdbot gateway status
|
||||||
|
clawdbot doctor --non-interactive
|
||||||
|
journalctl -u clawdbot --no-pager -n 50
|
||||||
|
```
|
||||||
|
|
||||||
|
### Port already in use
|
||||||
|
```bash
|
||||||
|
lsof -i :18789
|
||||||
|
kill <PID>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Out of memory
|
||||||
|
```bash
|
||||||
|
# Check memory
|
||||||
|
free -h
|
||||||
|
|
||||||
|
# Add more swap
|
||||||
|
# Or upgrade to $12/mo droplet (2GB RAM)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Hetzner guide](/platforms/hetzner) — cheaper, more powerful
|
||||||
|
- [Docker install](/install/docker) — containerized setup
|
||||||
|
- [Tailscale](/gateway/tailscale) — secure remote access
|
||||||
|
- [Configuration](/gateway/configuration) — full config reference
|
||||||
@@ -39,7 +39,9 @@ fly volumes create clawdbot_data --size 1 --region iad
|
|||||||
|
|
||||||
## 2) Configure fly.toml
|
## 2) Configure fly.toml
|
||||||
|
|
||||||
Edit `fly.toml` to match your app name and requirements:
|
Edit `fly.toml` to match your app name and requirements.
|
||||||
|
|
||||||
|
**Security note:** The default config exposes a public URL. For a hardened deployment with no public IP, see [Private Deployment](#private-deployment-hardened) or use `fly.private.toml`.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
app = "my-clawdbot" # Your app name
|
app = "my-clawdbot" # Your app name
|
||||||
@@ -80,6 +82,7 @@ primary_region = "iad"
|
|||||||
|---------|-----|
|
|---------|-----|
|
||||||
| `--bind lan` | Binds to `0.0.0.0` so Fly's proxy can reach the gateway |
|
| `--bind lan` | Binds to `0.0.0.0` so Fly's proxy can reach the gateway |
|
||||||
| `--allow-unconfigured` | Starts without a config file (you'll create one after) |
|
| `--allow-unconfigured` | Starts without a config file (you'll create one after) |
|
||||||
|
| `internal_port = 3000` | Must match `--port 3000` (or `CLAWDBOT_GATEWAY_PORT`) for Fly health checks |
|
||||||
| `memory = "2048mb"` | 512MB is too small; 2GB recommended |
|
| `memory = "2048mb"` | 512MB is too small; 2GB recommended |
|
||||||
| `CLAWDBOT_STATE_DIR = "/data"` | Persists state on the volume |
|
| `CLAWDBOT_STATE_DIR = "/data"` | Persists state on the volume |
|
||||||
|
|
||||||
@@ -103,6 +106,7 @@ fly secrets set DISCORD_BOT_TOKEN=MTQ...
|
|||||||
**Notes:**
|
**Notes:**
|
||||||
- Non-loopback binds (`--bind lan`) require `CLAWDBOT_GATEWAY_TOKEN` for security.
|
- Non-loopback binds (`--bind lan`) require `CLAWDBOT_GATEWAY_TOKEN` for security.
|
||||||
- Treat these tokens like passwords.
|
- Treat these tokens like passwords.
|
||||||
|
- **Prefer env vars over config file** for all API keys and tokens. This keeps secrets out of `clawdbot.json` where they could be accidentally exposed or logged.
|
||||||
|
|
||||||
## 4) Deploy
|
## 4) Deploy
|
||||||
|
|
||||||
@@ -181,7 +185,7 @@ cat > /data/clawdbot.json << 'EOF'
|
|||||||
"bind": "auto"
|
"bind": "auto"
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"lastTouchedVersion": "2026.1.24"
|
"lastTouchedVersion": "2026.1.25"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
@@ -235,6 +239,12 @@ The gateway is binding to `127.0.0.1` instead of `0.0.0.0`.
|
|||||||
|
|
||||||
**Fix:** Add `--bind lan` to your process command in `fly.toml`.
|
**Fix:** Add `--bind lan` to your process command in `fly.toml`.
|
||||||
|
|
||||||
|
### Health checks failing / connection refused
|
||||||
|
|
||||||
|
Fly can't reach the gateway on the configured port.
|
||||||
|
|
||||||
|
**Fix:** Ensure `internal_port` matches the gateway port (set `--port 3000` or `CLAWDBOT_GATEWAY_PORT=3000`).
|
||||||
|
|
||||||
### OOM / Memory Issues
|
### OOM / Memory Issues
|
||||||
|
|
||||||
Container keeps restarting or getting killed. Signs: `SIGABRT`, `v8::internal::Runtime_AllocateInYoungGeneration`, or silent restarts.
|
Container keeps restarting or getting killed. Signs: `SIGABRT`, `v8::internal::Runtime_AllocateInYoungGeneration`, or silent restarts.
|
||||||
@@ -268,11 +278,11 @@ The lock file is at `/data/gateway.*.lock` (not in a subdirectory).
|
|||||||
|
|
||||||
### Config Not Being Read
|
### Config Not Being Read
|
||||||
|
|
||||||
If using `--allow-unconfigured`, the gateway creates a minimal config. Your custom config at `/data/.clawdbot/clawdbot.json` should be read on restart.
|
If using `--allow-unconfigured`, the gateway creates a minimal config. Your custom config at `/data/clawdbot.json` should be read on restart.
|
||||||
|
|
||||||
Verify the config exists:
|
Verify the config exists:
|
||||||
```bash
|
```bash
|
||||||
fly ssh console --command "cat /data/.clawdbot/clawdbot.json"
|
fly ssh console --command "cat /data/clawdbot.json"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Writing Config via SSH
|
### Writing Config via SSH
|
||||||
@@ -281,18 +291,24 @@ The `fly ssh console -C` command doesn't support shell redirection. To write a c
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Use echo + tee (pipe from local to remote)
|
# Use echo + tee (pipe from local to remote)
|
||||||
echo '{"your":"config"}' | fly ssh console -C "tee /data/.clawdbot/clawdbot.json"
|
echo '{"your":"config"}' | fly ssh console -C "tee /data/clawdbot.json"
|
||||||
|
|
||||||
# Or use sftp
|
# Or use sftp
|
||||||
fly sftp shell
|
fly sftp shell
|
||||||
> put /local/path/config.json /data/.clawdbot/clawdbot.json
|
> put /local/path/config.json /data/clawdbot.json
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** `fly sftp` may fail if the file already exists. Delete first:
|
**Note:** `fly sftp` may fail if the file already exists. Delete first:
|
||||||
```bash
|
```bash
|
||||||
fly ssh console --command "rm /data/.clawdbot/clawdbot.json"
|
fly ssh console --command "rm /data/clawdbot.json"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### State Not Persisting
|
||||||
|
|
||||||
|
If you lose credentials or sessions after a restart, the state dir is writing to the container filesystem.
|
||||||
|
|
||||||
|
**Fix:** Ensure `CLAWDBOT_STATE_DIR=/data` is set in `fly.toml` and redeploy.
|
||||||
|
|
||||||
## Updates
|
## Updates
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -324,12 +340,121 @@ fly machine update <machine-id> --vm-memory 2048 --command "node dist/index.js g
|
|||||||
|
|
||||||
**Note:** After `fly deploy`, the machine command may reset to what's in `fly.toml`. If you made manual changes, re-apply them after deploy.
|
**Note:** After `fly deploy`, the machine command may reset to what's in `fly.toml`. If you made manual changes, re-apply them after deploy.
|
||||||
|
|
||||||
|
## Private Deployment (Hardened)
|
||||||
|
|
||||||
|
By default, Fly allocates public IPs, making your gateway accessible at `https://your-app.fly.dev`. This is convenient but means your deployment is discoverable by internet scanners (Shodan, Censys, etc.).
|
||||||
|
|
||||||
|
For a hardened deployment with **no public exposure**, use the private template.
|
||||||
|
|
||||||
|
### When to use private deployment
|
||||||
|
|
||||||
|
- You only make **outbound** calls/messages (no inbound webhooks)
|
||||||
|
- You use **ngrok or Tailscale** tunnels for any webhook callbacks
|
||||||
|
- You access the gateway via **SSH, proxy, or WireGuard** instead of browser
|
||||||
|
- You want the deployment **hidden from internet scanners**
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
Use `fly.private.toml` instead of the standard config:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Deploy with private config
|
||||||
|
fly deploy -c fly.private.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
Or convert an existing deployment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List current IPs
|
||||||
|
fly ips list -a my-clawdbot
|
||||||
|
|
||||||
|
# Release public IPs
|
||||||
|
fly ips release <public-ipv4> -a my-clawdbot
|
||||||
|
fly ips release <public-ipv6> -a my-clawdbot
|
||||||
|
|
||||||
|
# Switch to private config so future deploys don't re-allocate public IPs
|
||||||
|
# (remove [http_service] or deploy with the private template)
|
||||||
|
fly deploy -c fly.private.toml
|
||||||
|
|
||||||
|
# Allocate private-only IPv6
|
||||||
|
fly ips allocate-v6 --private -a my-clawdbot
|
||||||
|
```
|
||||||
|
|
||||||
|
After this, `fly ips list` should show only a `private` type IP:
|
||||||
|
```
|
||||||
|
VERSION IP TYPE REGION
|
||||||
|
v6 fdaa:x:x:x:x::x private global
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accessing a private deployment
|
||||||
|
|
||||||
|
Since there's no public URL, use one of these methods:
|
||||||
|
|
||||||
|
**Option 1: Local proxy (simplest)**
|
||||||
|
```bash
|
||||||
|
# Forward local port 3000 to the app
|
||||||
|
fly proxy 3000:3000 -a my-clawdbot
|
||||||
|
|
||||||
|
# Then open http://localhost:3000 in browser
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: WireGuard VPN**
|
||||||
|
```bash
|
||||||
|
# Create WireGuard config (one-time)
|
||||||
|
fly wireguard create
|
||||||
|
|
||||||
|
# Import to WireGuard client, then access via internal IPv6
|
||||||
|
# Example: http://[fdaa:x:x:x:x::x]:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 3: SSH only**
|
||||||
|
```bash
|
||||||
|
fly ssh console -a my-clawdbot
|
||||||
|
```
|
||||||
|
|
||||||
|
### Webhooks with private deployment
|
||||||
|
|
||||||
|
If you need webhook callbacks (Twilio, Telnyx, etc.) without public exposure:
|
||||||
|
|
||||||
|
1. **ngrok tunnel** - Run ngrok inside the container or as a sidecar
|
||||||
|
2. **Tailscale Funnel** - Expose specific paths via Tailscale
|
||||||
|
3. **Outbound-only** - Some providers (Twilio) work fine for outbound calls without webhooks
|
||||||
|
|
||||||
|
Example voice-call config with ngrok:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plugins": {
|
||||||
|
"entries": {
|
||||||
|
"voice-call": {
|
||||||
|
"enabled": true,
|
||||||
|
"config": {
|
||||||
|
"provider": "twilio",
|
||||||
|
"tunnel": { "provider": "ngrok" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The ngrok tunnel runs inside the container and provides a public webhook URL without exposing the Fly app itself.
|
||||||
|
|
||||||
|
### Security benefits
|
||||||
|
|
||||||
|
| Aspect | Public | Private |
|
||||||
|
|--------|--------|---------|
|
||||||
|
| Internet scanners | Discoverable | Hidden |
|
||||||
|
| Direct attacks | Possible | Blocked |
|
||||||
|
| Control UI access | Browser | Proxy/VPN |
|
||||||
|
| Webhook delivery | Direct | Via tunnel |
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Fly.io uses **x86 architecture** (not ARM)
|
- Fly.io uses **x86 architecture** (not ARM)
|
||||||
- The Dockerfile is compatible with both architectures
|
- The Dockerfile is compatible with both architectures
|
||||||
- For WhatsApp/Telegram onboarding, use `fly ssh console`
|
- For WhatsApp/Telegram onboarding, use `fly ssh console`
|
||||||
- Persistent data lives on the volume at `/data`
|
- Persistent data lives on the volume at `/data`
|
||||||
|
- Signal requires Java + signal-cli; use a custom image and keep memory at 2GB+.
|
||||||
|
|
||||||
## Cost
|
## Cost
|
||||||
|
|
||||||
|
|||||||
498
docs/platforms/gcp.md
Normal file
498
docs/platforms/gcp.md
Normal file
@@ -0,0 +1,498 @@
|
|||||||
|
---
|
||||||
|
summary: "Run Clawdbot Gateway 24/7 on a GCP Compute Engine VM (Docker) with durable state"
|
||||||
|
read_when:
|
||||||
|
- You want Clawdbot running 24/7 on GCP
|
||||||
|
- You want a production-grade, always-on Gateway on your own VM
|
||||||
|
- You want full control over persistence, binaries, and restart behavior
|
||||||
|
---
|
||||||
|
|
||||||
|
# Clawdbot on GCP Compute Engine (Docker, Production VPS Guide)
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Run a persistent Clawdbot Gateway on a GCP Compute Engine VM using Docker, with durable state, baked-in binaries, and safe restart behavior.
|
||||||
|
|
||||||
|
If you want "Clawdbot 24/7 for ~$5-12/mo", this is a reliable setup on Google Cloud.
|
||||||
|
Pricing varies by machine type and region; pick the smallest VM that fits your workload and scale up if you hit OOMs.
|
||||||
|
|
||||||
|
## What are we doing (simple terms)?
|
||||||
|
|
||||||
|
- Create a GCP project and enable billing
|
||||||
|
- Create a Compute Engine VM
|
||||||
|
- Install Docker (isolated app runtime)
|
||||||
|
- Start the Clawdbot Gateway in Docker
|
||||||
|
- Persist `~/.clawdbot` + `~/clawd` on the host (survives restarts/rebuilds)
|
||||||
|
- Access the Control UI from your laptop via an SSH tunnel
|
||||||
|
|
||||||
|
The Gateway can be accessed via:
|
||||||
|
- SSH port forwarding from your laptop
|
||||||
|
- Direct port exposure if you manage firewalling and tokens yourself
|
||||||
|
|
||||||
|
This guide uses Debian on GCP Compute Engine.
|
||||||
|
Ubuntu also works; map packages accordingly.
|
||||||
|
For the generic Docker flow, see [Docker](/install/docker).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick path (experienced operators)
|
||||||
|
|
||||||
|
1) Create GCP project + enable Compute Engine API
|
||||||
|
2) Create Compute Engine VM (e2-small, Debian 12, 20GB)
|
||||||
|
3) SSH into the VM
|
||||||
|
4) Install Docker
|
||||||
|
5) Clone Clawdbot repository
|
||||||
|
6) Create persistent host directories
|
||||||
|
7) Configure `.env` and `docker-compose.yml`
|
||||||
|
8) Bake required binaries, build, and launch
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What you need
|
||||||
|
|
||||||
|
- GCP account (free tier eligible for e2-micro)
|
||||||
|
- gcloud CLI installed (or use Cloud Console)
|
||||||
|
- SSH access from your laptop
|
||||||
|
- Basic comfort with SSH + copy/paste
|
||||||
|
- ~20-30 minutes
|
||||||
|
- Docker and Docker Compose
|
||||||
|
- Model auth credentials
|
||||||
|
- Optional provider credentials
|
||||||
|
- WhatsApp QR
|
||||||
|
- Telegram bot token
|
||||||
|
- Gmail OAuth
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1) Install gcloud CLI (or use Console)
|
||||||
|
|
||||||
|
**Option A: gcloud CLI** (recommended for automation)
|
||||||
|
|
||||||
|
Install from https://cloud.google.com/sdk/docs/install
|
||||||
|
|
||||||
|
Initialize and authenticate:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gcloud init
|
||||||
|
gcloud auth login
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option B: Cloud Console**
|
||||||
|
|
||||||
|
All steps can be done via the web UI at https://console.cloud.google.com
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2) Create a GCP project
|
||||||
|
|
||||||
|
**CLI:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gcloud projects create my-clawdbot-project --name="Clawdbot Gateway"
|
||||||
|
gcloud config set project my-clawdbot-project
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable billing at https://console.cloud.google.com/billing (required for Compute Engine).
|
||||||
|
|
||||||
|
Enable the Compute Engine API:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gcloud services enable compute.googleapis.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**Console:**
|
||||||
|
|
||||||
|
1. Go to IAM & Admin > Create Project
|
||||||
|
2. Name it and create
|
||||||
|
3. Enable billing for the project
|
||||||
|
4. Navigate to APIs & Services > Enable APIs > search "Compute Engine API" > Enable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3) Create the VM
|
||||||
|
|
||||||
|
**Machine types:**
|
||||||
|
|
||||||
|
| Type | Specs | Cost | Notes |
|
||||||
|
|------|-------|------|-------|
|
||||||
|
| e2-small | 2 vCPU, 2GB RAM | ~$12/mo | Recommended |
|
||||||
|
| e2-micro | 2 vCPU (shared), 1GB RAM | Free tier eligible | May OOM under load |
|
||||||
|
|
||||||
|
**CLI:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gcloud compute instances create clawdbot-gateway \
|
||||||
|
--zone=us-central1-a \
|
||||||
|
--machine-type=e2-small \
|
||||||
|
--boot-disk-size=20GB \
|
||||||
|
--image-family=debian-12 \
|
||||||
|
--image-project=debian-cloud
|
||||||
|
```
|
||||||
|
|
||||||
|
**Console:**
|
||||||
|
|
||||||
|
1. Go to Compute Engine > VM instances > Create instance
|
||||||
|
2. Name: `clawdbot-gateway`
|
||||||
|
3. Region: `us-central1`, Zone: `us-central1-a`
|
||||||
|
4. Machine type: `e2-small`
|
||||||
|
5. Boot disk: Debian 12, 20GB
|
||||||
|
6. Create
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4) SSH into the VM
|
||||||
|
|
||||||
|
**CLI:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gcloud compute ssh clawdbot-gateway --zone=us-central1-a
|
||||||
|
```
|
||||||
|
|
||||||
|
**Console:**
|
||||||
|
|
||||||
|
Click the "SSH" button next to your VM in the Compute Engine dashboard.
|
||||||
|
|
||||||
|
Note: SSH key propagation can take 1-2 minutes after VM creation. If connection is refused, wait and retry.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5) Install Docker (on the VM)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y git curl ca-certificates
|
||||||
|
curl -fsSL https://get.docker.com | sudo sh
|
||||||
|
sudo usermod -aG docker $USER
|
||||||
|
```
|
||||||
|
|
||||||
|
Log out and back in for the group change to take effect:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
exit
|
||||||
|
```
|
||||||
|
|
||||||
|
Then SSH back in:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gcloud compute ssh clawdbot-gateway --zone=us-central1-a
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker --version
|
||||||
|
docker compose version
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6) Clone the Clawdbot repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/clawdbot/clawdbot.git
|
||||||
|
cd clawdbot
|
||||||
|
```
|
||||||
|
|
||||||
|
This guide assumes you will build a custom image to guarantee binary persistence.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7) Create persistent host directories
|
||||||
|
|
||||||
|
Docker containers are ephemeral.
|
||||||
|
All long-lived state must live on the host.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.clawdbot
|
||||||
|
mkdir -p ~/clawd
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8) Configure environment variables
|
||||||
|
|
||||||
|
Create `.env` in the repository root.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CLAWDBOT_IMAGE=clawdbot:latest
|
||||||
|
CLAWDBOT_GATEWAY_TOKEN=change-me-now
|
||||||
|
CLAWDBOT_GATEWAY_BIND=lan
|
||||||
|
CLAWDBOT_GATEWAY_PORT=18789
|
||||||
|
|
||||||
|
CLAWDBOT_CONFIG_DIR=/home/$USER/.clawdbot
|
||||||
|
CLAWDBOT_WORKSPACE_DIR=/home/$USER/clawd
|
||||||
|
|
||||||
|
GOG_KEYRING_PASSWORD=change-me-now
|
||||||
|
XDG_CONFIG_HOME=/home/node/.clawdbot
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate strong secrets:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openssl rand -hex 32
|
||||||
|
```
|
||||||
|
|
||||||
|
**Do not commit this file.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9) Docker Compose configuration
|
||||||
|
|
||||||
|
Create or update `docker-compose.yml`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
clawdbot-gateway:
|
||||||
|
image: ${CLAWDBOT_IMAGE}
|
||||||
|
build: .
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- HOME=/home/node
|
||||||
|
- NODE_ENV=production
|
||||||
|
- TERM=xterm-256color
|
||||||
|
- CLAWDBOT_GATEWAY_BIND=${CLAWDBOT_GATEWAY_BIND}
|
||||||
|
- CLAWDBOT_GATEWAY_PORT=${CLAWDBOT_GATEWAY_PORT}
|
||||||
|
- CLAWDBOT_GATEWAY_TOKEN=${CLAWDBOT_GATEWAY_TOKEN}
|
||||||
|
- GOG_KEYRING_PASSWORD=${GOG_KEYRING_PASSWORD}
|
||||||
|
- XDG_CONFIG_HOME=${XDG_CONFIG_HOME}
|
||||||
|
- PATH=/home/linuxbrew/.linuxbrew/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
volumes:
|
||||||
|
- ${CLAWDBOT_CONFIG_DIR}:/home/node/.clawdbot
|
||||||
|
- ${CLAWDBOT_WORKSPACE_DIR}:/home/node/clawd
|
||||||
|
ports:
|
||||||
|
# Recommended: keep the Gateway loopback-only on the VM; access via SSH tunnel.
|
||||||
|
# To expose it publicly, remove the `127.0.0.1:` prefix and firewall accordingly.
|
||||||
|
- "127.0.0.1:${CLAWDBOT_GATEWAY_PORT}:18789"
|
||||||
|
|
||||||
|
# Optional: only if you run iOS/Android nodes against this VM and need Canvas host.
|
||||||
|
# If you expose this publicly, read /gateway/security and firewall accordingly.
|
||||||
|
# - "18793:18793"
|
||||||
|
command:
|
||||||
|
[
|
||||||
|
"node",
|
||||||
|
"dist/index.js",
|
||||||
|
"gateway",
|
||||||
|
"--bind",
|
||||||
|
"${CLAWDBOT_GATEWAY_BIND}",
|
||||||
|
"--port",
|
||||||
|
"${CLAWDBOT_GATEWAY_PORT}"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10) Bake required binaries into the image (critical)
|
||||||
|
|
||||||
|
Installing binaries inside a running container is a trap.
|
||||||
|
Anything installed at runtime will be lost on restart.
|
||||||
|
|
||||||
|
All external binaries required by skills must be installed at image build time.
|
||||||
|
|
||||||
|
The examples below show three common binaries only:
|
||||||
|
- `gog` for Gmail access
|
||||||
|
- `goplaces` for Google Places
|
||||||
|
- `wacli` for WhatsApp
|
||||||
|
|
||||||
|
These are examples, not a complete list.
|
||||||
|
You may install as many binaries as needed using the same pattern.
|
||||||
|
|
||||||
|
If you add new skills later that depend on additional binaries, you must:
|
||||||
|
1. Update the Dockerfile
|
||||||
|
2. Rebuild the image
|
||||||
|
3. Restart the containers
|
||||||
|
|
||||||
|
**Example Dockerfile**
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM node:22-bookworm
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y socat && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Example binary 1: Gmail CLI
|
||||||
|
RUN curl -L https://github.com/steipete/gog/releases/latest/download/gog_Linux_x86_64.tar.gz \
|
||||||
|
| tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/gog
|
||||||
|
|
||||||
|
# Example binary 2: Google Places CLI
|
||||||
|
RUN curl -L https://github.com/steipete/goplaces/releases/latest/download/goplaces_Linux_x86_64.tar.gz \
|
||||||
|
| tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/goplaces
|
||||||
|
|
||||||
|
# Example binary 3: WhatsApp CLI
|
||||||
|
RUN curl -L https://github.com/steipete/wacli/releases/latest/download/wacli_Linux_x86_64.tar.gz \
|
||||||
|
| tar -xz -C /usr/local/bin && chmod +x /usr/local/bin/wacli
|
||||||
|
|
||||||
|
# Add more binaries below using the same pattern
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
|
||||||
|
COPY ui/package.json ./ui/package.json
|
||||||
|
COPY scripts ./scripts
|
||||||
|
|
||||||
|
RUN corepack enable
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN pnpm build
|
||||||
|
RUN pnpm ui:install
|
||||||
|
RUN pnpm ui:build
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
CMD ["node","dist/index.js"]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11) Build and launch
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose build
|
||||||
|
docker compose up -d clawdbot-gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify binaries:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec clawdbot-gateway which gog
|
||||||
|
docker compose exec clawdbot-gateway which goplaces
|
||||||
|
docker compose exec clawdbot-gateway which wacli
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected output:
|
||||||
|
|
||||||
|
```
|
||||||
|
/usr/local/bin/gog
|
||||||
|
/usr/local/bin/goplaces
|
||||||
|
/usr/local/bin/wacli
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12) Verify Gateway
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose logs -f clawdbot-gateway
|
||||||
|
```
|
||||||
|
|
||||||
|
Success:
|
||||||
|
|
||||||
|
```
|
||||||
|
[gateway] listening on ws://0.0.0.0:18789
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13) Access from your laptop
|
||||||
|
|
||||||
|
Create an SSH tunnel to forward the Gateway port:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gcloud compute ssh clawdbot-gateway --zone=us-central1-a -- -L 18789:127.0.0.1:18789
|
||||||
|
```
|
||||||
|
|
||||||
|
Open in your browser:
|
||||||
|
|
||||||
|
`http://127.0.0.1:18789/`
|
||||||
|
|
||||||
|
Paste your gateway token.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What persists where (source of truth)
|
||||||
|
|
||||||
|
Clawdbot runs in Docker, but Docker is not the source of truth.
|
||||||
|
All long-lived state must survive restarts, rebuilds, and reboots.
|
||||||
|
|
||||||
|
| Component | Location | Persistence mechanism | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Gateway config | `/home/node/.clawdbot/` | Host volume mount | Includes `clawdbot.json`, tokens |
|
||||||
|
| Model auth profiles | `/home/node/.clawdbot/` | Host volume mount | OAuth tokens, API keys |
|
||||||
|
| Skill configs | `/home/node/.clawdbot/skills/` | Host volume mount | Skill-level state |
|
||||||
|
| Agent workspace | `/home/node/clawd/` | Host volume mount | Code and agent artifacts |
|
||||||
|
| WhatsApp session | `/home/node/.clawdbot/` | Host volume mount | Preserves QR login |
|
||||||
|
| Gmail keyring | `/home/node/.clawdbot/` | Host volume + password | Requires `GOG_KEYRING_PASSWORD` |
|
||||||
|
| External binaries | `/usr/local/bin/` | Docker image | Must be baked at build time |
|
||||||
|
| Node runtime | Container filesystem | Docker image | Rebuilt every image build |
|
||||||
|
| OS packages | Container filesystem | Docker image | Do not install at runtime |
|
||||||
|
| Docker container | Ephemeral | Restartable | Safe to destroy |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Updates
|
||||||
|
|
||||||
|
To update Clawdbot on the VM:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/clawdbot
|
||||||
|
git pull
|
||||||
|
docker compose build
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**SSH connection refused**
|
||||||
|
|
||||||
|
SSH key propagation can take 1-2 minutes after VM creation. Wait and retry.
|
||||||
|
|
||||||
|
**OS Login issues**
|
||||||
|
|
||||||
|
Check your OS Login profile:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gcloud compute os-login describe-profile
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensure your account has the required IAM permissions (Compute OS Login or Compute OS Admin Login).
|
||||||
|
|
||||||
|
**Out of memory (OOM)**
|
||||||
|
|
||||||
|
If using e2-micro and hitting OOM, upgrade to e2-small or e2-medium:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop the VM first
|
||||||
|
gcloud compute instances stop clawdbot-gateway --zone=us-central1-a
|
||||||
|
|
||||||
|
# Change machine type
|
||||||
|
gcloud compute instances set-machine-type clawdbot-gateway \
|
||||||
|
--zone=us-central1-a \
|
||||||
|
--machine-type=e2-small
|
||||||
|
|
||||||
|
# Start the VM
|
||||||
|
gcloud compute instances start clawdbot-gateway --zone=us-central1-a
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Service accounts (security best practice)
|
||||||
|
|
||||||
|
For personal use, your default user account works fine.
|
||||||
|
|
||||||
|
For automation or CI/CD pipelines, create a dedicated service account with minimal permissions:
|
||||||
|
|
||||||
|
1. Create a service account:
|
||||||
|
```bash
|
||||||
|
gcloud iam service-accounts create clawdbot-deploy \
|
||||||
|
--display-name="Clawdbot Deployment"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Grant Compute Instance Admin role (or narrower custom role):
|
||||||
|
```bash
|
||||||
|
gcloud projects add-iam-policy-binding my-clawdbot-project \
|
||||||
|
--member="serviceAccount:clawdbot-deploy@my-clawdbot-project.iam.gserviceaccount.com" \
|
||||||
|
--role="roles/compute.instanceAdmin.v1"
|
||||||
|
```
|
||||||
|
|
||||||
|
Avoid using the Owner role for automation. Use the principle of least privilege.
|
||||||
|
|
||||||
|
See https://cloud.google.com/iam/docs/understanding-roles for IAM role details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
|
||||||
|
- Set up messaging channels: [Channels](/channels)
|
||||||
|
- Pair local devices as nodes: [Nodes](/nodes)
|
||||||
|
- Configure the Gateway: [Gateway configuration](/gateway/configuration)
|
||||||
@@ -23,8 +23,10 @@ Native companion apps for Windows are also planned; the Gateway is recommended v
|
|||||||
|
|
||||||
## VPS & hosting
|
## VPS & hosting
|
||||||
|
|
||||||
|
- VPS hub: [VPS hosting](/vps)
|
||||||
- Fly.io: [Fly.io](/platforms/fly)
|
- Fly.io: [Fly.io](/platforms/fly)
|
||||||
- Hetzner (Docker): [Hetzner](/platforms/hetzner)
|
- Hetzner (Docker): [Hetzner](/platforms/hetzner)
|
||||||
|
- GCP (Compute Engine): [GCP](/platforms/gcp)
|
||||||
- exe.dev (VM + HTTPS proxy): [exe.dev](/platforms/exe-dev)
|
- exe.dev (VM + HTTPS proxy): [exe.dev](/platforms/exe-dev)
|
||||||
|
|
||||||
## Common links
|
## Common links
|
||||||
|
|||||||
@@ -30,17 +30,17 @@ Notes:
|
|||||||
# From repo root; set release IDs so Sparkle feed is enabled.
|
# From repo root; set release IDs so Sparkle feed is enabled.
|
||||||
# APP_BUILD must be numeric + monotonic for Sparkle compare.
|
# APP_BUILD must be numeric + monotonic for Sparkle compare.
|
||||||
BUNDLE_ID=com.clawdbot.mac \
|
BUNDLE_ID=com.clawdbot.mac \
|
||||||
APP_VERSION=2026.1.24 \
|
APP_VERSION=2026.1.25 \
|
||||||
APP_BUILD="$(git rev-list --count HEAD)" \
|
APP_BUILD="$(git rev-list --count HEAD)" \
|
||||||
BUILD_CONFIG=release \
|
BUILD_CONFIG=release \
|
||||||
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
|
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
|
||||||
scripts/package-mac-app.sh
|
scripts/package-mac-app.sh
|
||||||
|
|
||||||
# Zip for distribution (includes resource forks for Sparkle delta support)
|
# Zip for distribution (includes resource forks for Sparkle delta support)
|
||||||
ditto -c -k --sequesterRsrc --keepParent dist/Clawdbot.app dist/Clawdbot-2026.1.24.zip
|
ditto -c -k --sequesterRsrc --keepParent dist/Clawdbot.app dist/Clawdbot-2026.1.25.zip
|
||||||
|
|
||||||
# Optional: also build a styled DMG for humans (drag to /Applications)
|
# Optional: also build a styled DMG for humans (drag to /Applications)
|
||||||
scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.24.dmg
|
scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.25.dmg
|
||||||
|
|
||||||
# Recommended: build + notarize/staple zip + DMG
|
# Recommended: build + notarize/staple zip + DMG
|
||||||
# First, create a keychain profile once:
|
# First, create a keychain profile once:
|
||||||
@@ -48,26 +48,26 @@ scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.24.dmg
|
|||||||
# --apple-id "<apple-id>" --team-id "<team-id>" --password "<app-specific-password>"
|
# --apple-id "<apple-id>" --team-id "<team-id>" --password "<app-specific-password>"
|
||||||
NOTARIZE=1 NOTARYTOOL_PROFILE=clawdbot-notary \
|
NOTARIZE=1 NOTARYTOOL_PROFILE=clawdbot-notary \
|
||||||
BUNDLE_ID=com.clawdbot.mac \
|
BUNDLE_ID=com.clawdbot.mac \
|
||||||
APP_VERSION=2026.1.24 \
|
APP_VERSION=2026.1.25 \
|
||||||
APP_BUILD="$(git rev-list --count HEAD)" \
|
APP_BUILD="$(git rev-list --count HEAD)" \
|
||||||
BUILD_CONFIG=release \
|
BUILD_CONFIG=release \
|
||||||
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
|
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
|
||||||
scripts/package-mac-dist.sh
|
scripts/package-mac-dist.sh
|
||||||
|
|
||||||
# Optional: ship dSYM alongside the release
|
# Optional: ship dSYM alongside the release
|
||||||
ditto -c -k --keepParent apps/macos/.build/release/Clawdbot.app.dSYM dist/Clawdbot-2026.1.24.dSYM.zip
|
ditto -c -k --keepParent apps/macos/.build/release/Clawdbot.app.dSYM dist/Clawdbot-2026.1.25.dSYM.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
## Appcast entry
|
## Appcast entry
|
||||||
Use the release note generator so Sparkle renders formatted HTML notes:
|
Use the release note generator so Sparkle renders formatted HTML notes:
|
||||||
```bash
|
```bash
|
||||||
SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/Clawdbot-2026.1.24.zip https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml
|
SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/Clawdbot-2026.1.25.zip https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml
|
||||||
```
|
```
|
||||||
Generates HTML release notes from `CHANGELOG.md` (via [`scripts/changelog-to-html.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/changelog-to-html.sh)) and embeds them in the appcast entry.
|
Generates HTML release notes from `CHANGELOG.md` (via [`scripts/changelog-to-html.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/changelog-to-html.sh)) and embeds them in the appcast entry.
|
||||||
Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when publishing.
|
Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when publishing.
|
||||||
|
|
||||||
## Publish & verify
|
## Publish & verify
|
||||||
- Upload `Clawdbot-2026.1.24.zip` (and `Clawdbot-2026.1.24.dSYM.zip`) to the GitHub release for tag `v2026.1.24`.
|
- Upload `Clawdbot-2026.1.25.zip` (and `Clawdbot-2026.1.25.dSYM.zip`) to the GitHub release for tag `v2026.1.25`.
|
||||||
- Ensure the raw appcast URL matches the baked feed: `https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml`.
|
- Ensure the raw appcast URL matches the baked feed: `https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml`.
|
||||||
- Sanity checks:
|
- Sanity checks:
|
||||||
- `curl -I https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml` returns 200.
|
- `curl -I https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml` returns 200.
|
||||||
|
|||||||
@@ -10,7 +10,13 @@ This flow lets the macOS app act as a full remote control for a Clawdbot gateway
|
|||||||
|
|
||||||
## Modes
|
## Modes
|
||||||
- **Local (this Mac)**: Everything runs on the laptop. No SSH involved.
|
- **Local (this Mac)**: Everything runs on the laptop. No SSH involved.
|
||||||
- **Remote over SSH**: Clawdbot commands are executed on the remote host. The mac app opens an SSH connection with `-o BatchMode` plus your chosen identity/key.
|
- **Remote over SSH (default)**: Clawdbot commands are executed on the remote host. The mac app opens an SSH connection with `-o BatchMode` plus your chosen identity/key and a local port-forward.
|
||||||
|
- **Remote direct (ws/wss)**: No SSH tunnel. The mac app connects to the gateway URL directly (for example, via Tailscale Serve or a public HTTPS reverse proxy).
|
||||||
|
|
||||||
|
## Remote transports
|
||||||
|
Remote mode supports two transports:
|
||||||
|
- **SSH tunnel** (default): Uses `ssh -N -L ...` to forward the gateway port to localhost. The gateway will see the node’s IP as `127.0.0.1` because the tunnel is loopback.
|
||||||
|
- **Direct (ws/wss)**: Connects straight to the gateway URL. The gateway sees the real client IP.
|
||||||
|
|
||||||
## Prereqs on the remote host
|
## Prereqs on the remote host
|
||||||
1) Install Node + pnpm and build/install the Clawdbot CLI (`pnpm install && pnpm build && pnpm link --global`).
|
1) Install Node + pnpm and build/install the Clawdbot CLI (`pnpm install && pnpm build && pnpm link --global`).
|
||||||
@@ -20,16 +26,19 @@ This flow lets the macOS app act as a full remote control for a Clawdbot gateway
|
|||||||
## macOS app setup
|
## macOS app setup
|
||||||
1) Open *Settings → General*.
|
1) Open *Settings → General*.
|
||||||
2) Under **Clawdbot runs**, pick **Remote over SSH** and set:
|
2) Under **Clawdbot runs**, pick **Remote over SSH** and set:
|
||||||
|
- **Transport**: **SSH tunnel** or **Direct (ws/wss)**.
|
||||||
- **SSH target**: `user@host` (optional `:port`).
|
- **SSH target**: `user@host` (optional `:port`).
|
||||||
- If the gateway is on the same LAN and advertises Bonjour, pick it from the discovered list to auto-fill this field.
|
- If the gateway is on the same LAN and advertises Bonjour, pick it from the discovered list to auto-fill this field.
|
||||||
|
- **Gateway URL** (Direct only): `wss://gateway.example.ts.net` (or `ws://...` for local/LAN).
|
||||||
- **Identity file** (advanced): path to your key.
|
- **Identity file** (advanced): path to your key.
|
||||||
- **Project root** (advanced): remote checkout path used for commands.
|
- **Project root** (advanced): remote checkout path used for commands.
|
||||||
- **CLI path** (advanced): optional path to a runnable `clawdbot` entrypoint/binary (auto-filled when advertised).
|
- **CLI path** (advanced): optional path to a runnable `clawdbot` entrypoint/binary (auto-filled when advertised).
|
||||||
3) Hit **Test remote**. Success indicates the remote `clawdbot status --json` runs correctly. Failures usually mean PATH/CLI issues; exit 127 means the CLI isn’t found remotely.
|
3) Hit **Test remote**. Success indicates the remote `clawdbot status --json` runs correctly. Failures usually mean PATH/CLI issues; exit 127 means the CLI isn’t found remotely.
|
||||||
4) Health checks and Web Chat will now run through this SSH tunnel automatically.
|
4) Health checks and Web Chat will now run through this SSH tunnel automatically.
|
||||||
|
|
||||||
## Web Chat over SSH
|
## Web Chat
|
||||||
- Web Chat connects to the gateway over the forwarded WebSocket control port (default 18789).
|
- **SSH tunnel**: Web Chat connects to the gateway over the forwarded WebSocket control port (default 18789).
|
||||||
|
- **Direct (ws/wss)**: Web Chat connects straight to the configured gateway URL.
|
||||||
- There is no separate WebChat HTTP server anymore.
|
- There is no separate WebChat HTTP server anymore.
|
||||||
|
|
||||||
## Permissions
|
## Permissions
|
||||||
@@ -49,6 +58,7 @@ This flow lets the macOS app act as a full remote control for a Clawdbot gateway
|
|||||||
- **exit 127 / not found**: `clawdbot` isn’t on PATH for non-login shells. Add it to `/etc/paths`, your shell rc, or symlink into `/usr/local/bin`/`/opt/homebrew/bin`.
|
- **exit 127 / not found**: `clawdbot` isn’t on PATH for non-login shells. Add it to `/etc/paths`, your shell rc, or symlink into `/usr/local/bin`/`/opt/homebrew/bin`.
|
||||||
- **Health probe failed**: check SSH reachability, PATH, and that Baileys is logged in (`clawdbot status --json`).
|
- **Health probe failed**: check SSH reachability, PATH, and that Baileys is logged in (`clawdbot status --json`).
|
||||||
- **Web Chat stuck**: confirm the gateway is running on the remote host and the forwarded port matches the gateway WS port; the UI requires a healthy WS connection.
|
- **Web Chat stuck**: confirm the gateway is running on the remote host and the forwarded port matches the gateway WS port; the UI requires a healthy WS connection.
|
||||||
|
- **Node IP shows 127.0.0.1**: expected with the SSH tunnel. Switch **Transport** to **Direct (ws/wss)** if you want the gateway to see the real client IP.
|
||||||
- **Voice Wake**: trigger phrases are forwarded automatically in remote mode; no separate forwarder is needed.
|
- **Voice Wake**: trigger phrases are forwarded automatically in remote mode; no separate forwarder is needed.
|
||||||
|
|
||||||
## Notification sounds
|
## Notification sounds
|
||||||
|
|||||||
275
docs/platforms/macos-vm.md
Normal file
275
docs/platforms/macos-vm.md
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
---
|
||||||
|
summary: "Run Clawdbot in a sandboxed macOS VM (local or hosted) when you need isolation or iMessage"
|
||||||
|
read_when:
|
||||||
|
- You want Clawdbot isolated from your main macOS environment
|
||||||
|
- You want iMessage integration (BlueBubbles) in a sandbox
|
||||||
|
- You want a resettable macOS environment you can clone
|
||||||
|
- You want to compare local vs hosted macOS VM options
|
||||||
|
---
|
||||||
|
|
||||||
|
# Clawdbot on macOS VMs (Sandboxing)
|
||||||
|
|
||||||
|
## Recommended default (most users)
|
||||||
|
|
||||||
|
- **Small Linux VPS** for an always-on Gateway and low cost. See [VPS hosting](/vps).
|
||||||
|
- **Dedicated hardware** (Mac mini or Linux box) if you want full control and a **residential IP** for browser automation. Many sites block data center IPs, so local browsing often works better.
|
||||||
|
- **Hybrid:** keep the Gateway on a cheap VPS, and connect your Mac as a **node** when you need browser/UI automation. See [Nodes](/nodes) and [Gateway remote](/gateway/remote).
|
||||||
|
|
||||||
|
Use a macOS VM when you specifically need macOS-only capabilities (iMessage/BlueBubbles) or want strict isolation from your daily Mac.
|
||||||
|
|
||||||
|
## macOS VM options
|
||||||
|
|
||||||
|
### Local VM on your Apple Silicon Mac (Lume)
|
||||||
|
|
||||||
|
Run Clawdbot in a sandboxed macOS VM on your existing Apple Silicon Mac using [Lume](https://cua.ai/docs/lume).
|
||||||
|
|
||||||
|
This gives you:
|
||||||
|
- Full macOS environment in isolation (your host stays clean)
|
||||||
|
- iMessage support via BlueBubbles (impossible on Linux/Windows)
|
||||||
|
- Instant reset by cloning VMs
|
||||||
|
- No extra hardware or cloud costs
|
||||||
|
|
||||||
|
### Hosted Mac providers (cloud)
|
||||||
|
|
||||||
|
If you want macOS in the cloud, hosted Mac providers work too:
|
||||||
|
- [MacStadium](https://www.macstadium.com/) (hosted Macs)
|
||||||
|
- Other hosted Mac vendors also work; follow their VM + SSH docs
|
||||||
|
|
||||||
|
Once you have SSH access to a macOS VM, continue at step 6 below.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick path (Lume, experienced users)
|
||||||
|
|
||||||
|
1. Install Lume
|
||||||
|
2. `lume create clawdbot --os macos --ipsw latest`
|
||||||
|
3. Complete Setup Assistant, enable Remote Login (SSH)
|
||||||
|
4. `lume run clawdbot --no-display`
|
||||||
|
5. SSH in, install Clawdbot, configure channels
|
||||||
|
6. Done
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What you need (Lume)
|
||||||
|
|
||||||
|
- Apple Silicon Mac (M1/M2/M3/M4)
|
||||||
|
- macOS Sequoia or later on the host
|
||||||
|
- ~60 GB free disk space per VM
|
||||||
|
- ~20 minutes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1) Install Lume
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/lume/scripts/install.sh)"
|
||||||
|
```
|
||||||
|
|
||||||
|
If `~/.local/bin` isn't in your PATH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo 'export PATH="$PATH:$HOME/.local/bin"' >> ~/.zshrc && source ~/.zshrc
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lume --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Docs: [Lume Installation](https://cua.ai/docs/lume/guide/getting-started/installation)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2) Create the macOS VM
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lume create clawdbot --os macos --ipsw latest
|
||||||
|
```
|
||||||
|
|
||||||
|
This downloads macOS and creates the VM. A VNC window opens automatically.
|
||||||
|
|
||||||
|
Note: The download can take a while depending on your connection.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3) Complete Setup Assistant
|
||||||
|
|
||||||
|
In the VNC window:
|
||||||
|
1. Select language and region
|
||||||
|
2. Skip Apple ID (or sign in if you want iMessage later)
|
||||||
|
3. Create a user account (remember the username and password)
|
||||||
|
4. Skip all optional features
|
||||||
|
|
||||||
|
After setup completes, enable SSH:
|
||||||
|
1. Open System Settings → General → Sharing
|
||||||
|
2. Enable "Remote Login"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4) Get the VM's IP address
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lume get clawdbot
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for the IP address (usually `192.168.64.x`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5) SSH into the VM
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh youruser@192.168.64.X
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `youruser` with the account you created, and the IP with your VM's IP.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6) Install Clawdbot
|
||||||
|
|
||||||
|
Inside the VM:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g clawdbot@latest
|
||||||
|
clawdbot onboard --install-daemon
|
||||||
|
```
|
||||||
|
|
||||||
|
Follow the onboarding prompts to set up your model provider (Anthropic, OpenAI, etc.).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7) Configure channels
|
||||||
|
|
||||||
|
Edit the config file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano ~/.clawdbot/clawdbot.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Add your channels:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channels": {
|
||||||
|
"whatsapp": {
|
||||||
|
"dmPolicy": "allowlist",
|
||||||
|
"allowFrom": ["+15551234567"]
|
||||||
|
},
|
||||||
|
"telegram": {
|
||||||
|
"botToken": "YOUR_BOT_TOKEN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then login to WhatsApp (scan QR):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clawdbot channels login
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8) Run the VM headlessly
|
||||||
|
|
||||||
|
Stop the VM and restart without display:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lume stop clawdbot
|
||||||
|
lume run clawdbot --no-display
|
||||||
|
```
|
||||||
|
|
||||||
|
The VM runs in the background. Clawdbot's daemon keeps the gateway running.
|
||||||
|
|
||||||
|
To check status:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh youruser@192.168.64.X "clawdbot status"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bonus: iMessage integration
|
||||||
|
|
||||||
|
This is the killer feature of running on macOS. Use [BlueBubbles](https://bluebubbles.app) to add iMessage to Clawdbot.
|
||||||
|
|
||||||
|
Inside the VM:
|
||||||
|
|
||||||
|
1. Download BlueBubbles from bluebubbles.app
|
||||||
|
2. Sign in with your Apple ID
|
||||||
|
3. Enable the Web API and set a password
|
||||||
|
4. Point BlueBubbles webhooks at your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=<password>`)
|
||||||
|
|
||||||
|
Add to your Clawdbot config:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"channels": {
|
||||||
|
"bluebubbles": {
|
||||||
|
"serverUrl": "http://localhost:1234",
|
||||||
|
"password": "your-api-password",
|
||||||
|
"webhookPath": "/bluebubbles-webhook"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart the gateway. Now your agent can send and receive iMessages.
|
||||||
|
|
||||||
|
Full setup details: [BlueBubbles channel](/channels/bluebubbles)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Save a golden image
|
||||||
|
|
||||||
|
Before customizing further, snapshot your clean state:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lume stop clawdbot
|
||||||
|
lume clone clawdbot clawdbot-golden
|
||||||
|
```
|
||||||
|
|
||||||
|
Reset anytime:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lume stop clawdbot && lume delete clawdbot
|
||||||
|
lume clone clawdbot-golden clawdbot
|
||||||
|
lume run clawdbot --no-display
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Running 24/7
|
||||||
|
|
||||||
|
Keep the VM running by:
|
||||||
|
- Keeping your Mac plugged in
|
||||||
|
- Disabling sleep in System Settings → Energy Saver
|
||||||
|
- Using `caffeinate` if needed
|
||||||
|
|
||||||
|
For true always-on, consider a dedicated Mac mini or a small VPS. See [VPS hosting](/vps).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Problem | Solution |
|
||||||
|
|---------|----------|
|
||||||
|
| Can't SSH into VM | Check "Remote Login" is enabled in VM's System Settings |
|
||||||
|
| VM IP not showing | Wait for VM to fully boot, run `lume get clawdbot` again |
|
||||||
|
| Lume command not found | Add `~/.local/bin` to your PATH |
|
||||||
|
| WhatsApp QR not scanning | Ensure you're logged into the VM (not host) when running `clawdbot channels login` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related docs
|
||||||
|
|
||||||
|
- [VPS hosting](/vps)
|
||||||
|
- [Nodes](/nodes)
|
||||||
|
- [Gateway remote](/gateway/remote)
|
||||||
|
- [BlueBubbles channel](/channels/bluebubbles)
|
||||||
|
- [Lume Quickstart](https://cua.ai/docs/lume/guide/getting-started/quickstart)
|
||||||
|
- [Lume CLI Reference](https://cua.ai/docs/lume/reference/cli-reference)
|
||||||
|
- [Unattended VM Setup](https://cua.ai/docs/lume/guide/fundamentals/unattended-setup) (advanced)
|
||||||
|
- [Docker Sandboxing](/install/docker) (alternative isolation approach)
|
||||||
@@ -180,6 +180,9 @@ components can talk to a remote Gateway as if it were on localhost.
|
|||||||
or restarts it if needed.
|
or restarts it if needed.
|
||||||
- **SSH shape:** `ssh -N -L <local>:127.0.0.1:<remote>` with BatchMode +
|
- **SSH shape:** `ssh -N -L <local>:127.0.0.1:<remote>` with BatchMode +
|
||||||
ExitOnForwardFailure + keepalive options.
|
ExitOnForwardFailure + keepalive options.
|
||||||
|
- **IP reporting:** the SSH tunnel uses loopback, so the gateway will see the node
|
||||||
|
IP as `127.0.0.1`. Use **Direct (ws/wss)** transport if you want the real client
|
||||||
|
IP to appear (see [macOS remote access](/platforms/mac/remote)).
|
||||||
|
|
||||||
For setup steps, see [macOS remote access](/platforms/mac/remote). For protocol
|
For setup steps, see [macOS remote access](/platforms/mac/remote). For protocol
|
||||||
details, see [Gateway protocol](/gateway/protocol).
|
details, see [Gateway protocol](/gateway/protocol).
|
||||||
|
|||||||
354
docs/platforms/raspberry-pi.md
Normal file
354
docs/platforms/raspberry-pi.md
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
---
|
||||||
|
summary: "Clawdbot on Raspberry Pi (budget self-hosted setup)"
|
||||||
|
read_when:
|
||||||
|
- Setting up Clawdbot on a Raspberry Pi
|
||||||
|
- Running Clawdbot on ARM devices
|
||||||
|
- Building a cheap always-on personal AI
|
||||||
|
---
|
||||||
|
|
||||||
|
# Clawdbot on Raspberry Pi
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Run a persistent, always-on Clawdbot Gateway on a Raspberry Pi for **~$35-80** one-time cost (no monthly fees).
|
||||||
|
|
||||||
|
Perfect for:
|
||||||
|
- 24/7 personal AI assistant
|
||||||
|
- Home automation hub
|
||||||
|
- Low-power, always-available Telegram/WhatsApp bot
|
||||||
|
|
||||||
|
## Hardware Requirements
|
||||||
|
|
||||||
|
| Pi Model | RAM | Works? | Notes |
|
||||||
|
|----------|-----|--------|-------|
|
||||||
|
| **Pi 5** | 4GB/8GB | ✅ Best | Fastest, recommended |
|
||||||
|
| **Pi 4** | 4GB | ✅ Good | Sweet spot for most users |
|
||||||
|
| **Pi 4** | 2GB | ✅ OK | Works, add swap |
|
||||||
|
| **Pi 4** | 1GB | ⚠️ Tight | Possible with swap, minimal config |
|
||||||
|
| **Pi 3B+** | 1GB | ⚠️ Slow | Works but sluggish |
|
||||||
|
| **Pi Zero 2 W** | 512MB | ❌ | Not recommended |
|
||||||
|
|
||||||
|
**Minimum specs:** 1GB RAM, 1 core, 500MB disk
|
||||||
|
**Recommended:** 2GB+ RAM, 64-bit OS, 16GB+ SD card (or USB SSD)
|
||||||
|
|
||||||
|
## What You'll Need
|
||||||
|
|
||||||
|
- Raspberry Pi 4 or 5 (2GB+ recommended)
|
||||||
|
- MicroSD card (16GB+) or USB SSD (better performance)
|
||||||
|
- Power supply (official Pi PSU recommended)
|
||||||
|
- Network connection (Ethernet or WiFi)
|
||||||
|
- ~30 minutes
|
||||||
|
|
||||||
|
## 1) Flash the OS
|
||||||
|
|
||||||
|
Use **Raspberry Pi OS Lite (64-bit)** — no desktop needed for a headless server.
|
||||||
|
|
||||||
|
1. Download [Raspberry Pi Imager](https://www.raspberrypi.com/software/)
|
||||||
|
2. Choose OS: **Raspberry Pi OS Lite (64-bit)**
|
||||||
|
3. Click the gear icon (⚙️) to pre-configure:
|
||||||
|
- Set hostname: `gateway-host`
|
||||||
|
- Enable SSH
|
||||||
|
- Set username/password
|
||||||
|
- Configure WiFi (if not using Ethernet)
|
||||||
|
4. Flash to your SD card / USB drive
|
||||||
|
5. Insert and boot the Pi
|
||||||
|
|
||||||
|
## 2) Connect via SSH
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh user@gateway-host
|
||||||
|
# or use the IP address
|
||||||
|
ssh user@192.168.x.x
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3) System Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update system
|
||||||
|
sudo apt update && sudo apt upgrade -y
|
||||||
|
|
||||||
|
# Install essential packages
|
||||||
|
sudo apt install -y git curl build-essential
|
||||||
|
|
||||||
|
# Set timezone (important for cron/reminders)
|
||||||
|
sudo timedatectl set-timezone America/Chicago # Change to your timezone
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4) Install Node.js 22 (ARM64)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Node.js via NodeSource
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
||||||
|
sudo apt install -y nodejs
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
node --version # Should show v22.x.x
|
||||||
|
npm --version
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5) Add Swap (Important for 2GB or less)
|
||||||
|
|
||||||
|
Swap prevents out-of-memory crashes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create 2GB swap file
|
||||||
|
sudo fallocate -l 2G /swapfile
|
||||||
|
sudo chmod 600 /swapfile
|
||||||
|
sudo mkswap /swapfile
|
||||||
|
sudo swapon /swapfile
|
||||||
|
|
||||||
|
# Make permanent
|
||||||
|
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
|
||||||
|
|
||||||
|
# Optimize for low RAM (reduce swappiness)
|
||||||
|
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
|
||||||
|
sudo sysctl -p
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6) Install Clawdbot
|
||||||
|
|
||||||
|
### Option A: Standard Install (Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://clawd.bot/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option B: Hackable Install (For tinkering)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/clawdbot/clawdbot.git
|
||||||
|
cd clawdbot
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
npm link
|
||||||
|
```
|
||||||
|
|
||||||
|
The hackable install gives you direct access to logs and code — useful for debugging ARM-specific issues.
|
||||||
|
|
||||||
|
## 7) Run Onboarding
|
||||||
|
|
||||||
|
```bash
|
||||||
|
clawdbot onboard --install-daemon
|
||||||
|
```
|
||||||
|
|
||||||
|
Follow the wizard:
|
||||||
|
1. **Gateway mode:** Local
|
||||||
|
2. **Auth:** API keys recommended (OAuth can be finicky on headless Pi)
|
||||||
|
3. **Channels:** Telegram is easiest to start with
|
||||||
|
4. **Daemon:** Yes (systemd)
|
||||||
|
|
||||||
|
## 8) Verify Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check status
|
||||||
|
clawdbot status
|
||||||
|
|
||||||
|
# Check service
|
||||||
|
sudo systemctl status clawdbot
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
journalctl -u clawdbot -f
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9) Access the Dashboard
|
||||||
|
|
||||||
|
Since the Pi is headless, use an SSH tunnel:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From your laptop/desktop
|
||||||
|
ssh -L 18789:localhost:18789 user@gateway-host
|
||||||
|
|
||||||
|
# Then open in browser
|
||||||
|
open http://localhost:18789
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use Tailscale for always-on access:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On the Pi
|
||||||
|
curl -fsSL https://tailscale.com/install.sh | sh
|
||||||
|
sudo tailscale up
|
||||||
|
|
||||||
|
# Update config
|
||||||
|
clawdbot config set gateway.bind tailnet
|
||||||
|
sudo systemctl restart clawdbot
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Optimizations
|
||||||
|
|
||||||
|
### Use a USB SSD (Huge Improvement)
|
||||||
|
|
||||||
|
SD cards are slow and wear out. A USB SSD dramatically improves performance:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if booting from USB
|
||||||
|
lsblk
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Pi USB boot guide](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#usb-mass-storage-boot) for setup.
|
||||||
|
|
||||||
|
### Reduce Memory Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Disable GPU memory allocation (headless)
|
||||||
|
echo 'gpu_mem=16' | sudo tee -a /boot/config.txt
|
||||||
|
|
||||||
|
# Disable Bluetooth if not needed
|
||||||
|
sudo systemctl disable bluetooth
|
||||||
|
```
|
||||||
|
|
||||||
|
### Monitor Resources
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check memory
|
||||||
|
free -h
|
||||||
|
|
||||||
|
# Check CPU temperature
|
||||||
|
vcgencmd measure_temp
|
||||||
|
|
||||||
|
# Live monitoring
|
||||||
|
htop
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ARM-Specific Notes
|
||||||
|
|
||||||
|
### Binary Compatibility
|
||||||
|
|
||||||
|
Most Clawdbot features work on ARM64, but some external binaries may need ARM builds:
|
||||||
|
|
||||||
|
| Tool | ARM64 Status | Notes |
|
||||||
|
|------|--------------|-------|
|
||||||
|
| Node.js | ✅ | Works great |
|
||||||
|
| WhatsApp (Baileys) | ✅ | Pure JS, no issues |
|
||||||
|
| Telegram | ✅ | Pure JS, no issues |
|
||||||
|
| gog (Gmail CLI) | ⚠️ | Check for ARM release |
|
||||||
|
| Chromium (browser) | ✅ | `sudo apt install chromium-browser` |
|
||||||
|
|
||||||
|
If a skill fails, check if its binary has an ARM build. Many Go/Rust tools do; some don't.
|
||||||
|
|
||||||
|
### 32-bit vs 64-bit
|
||||||
|
|
||||||
|
**Always use 64-bit OS.** Node.js and many modern tools require it. Check with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uname -m
|
||||||
|
# Should show: aarch64 (64-bit) not armv7l (32-bit)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommended Model Setup
|
||||||
|
|
||||||
|
Since the Pi is just the Gateway (models run in the cloud), use API-based models:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"agents": {
|
||||||
|
"defaults": {
|
||||||
|
"model": {
|
||||||
|
"primary": "anthropic/claude-sonnet-4-20250514",
|
||||||
|
"fallbacks": ["openai/gpt-4o-mini"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Don't try to run local LLMs on a Pi** — even small models are too slow. Let Claude/GPT do the heavy lifting.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Auto-Start on Boot
|
||||||
|
|
||||||
|
The onboarding wizard sets this up, but to verify:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check service is enabled
|
||||||
|
sudo systemctl is-enabled clawdbot
|
||||||
|
|
||||||
|
# Enable if not
|
||||||
|
sudo systemctl enable clawdbot
|
||||||
|
|
||||||
|
# Start on boot
|
||||||
|
sudo systemctl start clawdbot
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Out of Memory (OOM)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check memory
|
||||||
|
free -h
|
||||||
|
|
||||||
|
# Add more swap (see Step 5)
|
||||||
|
# Or reduce services running on the Pi
|
||||||
|
```
|
||||||
|
|
||||||
|
### Slow Performance
|
||||||
|
|
||||||
|
- Use USB SSD instead of SD card
|
||||||
|
- Disable unused services: `sudo systemctl disable cups bluetooth avahi-daemon`
|
||||||
|
- Check CPU throttling: `vcgencmd get_throttled` (should return `0x0`)
|
||||||
|
|
||||||
|
### Service Won't Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check logs
|
||||||
|
journalctl -u clawdbot --no-pager -n 100
|
||||||
|
|
||||||
|
# Common fix: rebuild
|
||||||
|
cd ~/clawdbot # if using hackable install
|
||||||
|
npm run build
|
||||||
|
sudo systemctl restart clawdbot
|
||||||
|
```
|
||||||
|
|
||||||
|
### ARM Binary Issues
|
||||||
|
|
||||||
|
If a skill fails with "exec format error":
|
||||||
|
1. Check if the binary has an ARM64 build
|
||||||
|
2. Try building from source
|
||||||
|
3. Or use a Docker container with ARM support
|
||||||
|
|
||||||
|
### WiFi Drops
|
||||||
|
|
||||||
|
For headless Pis on WiFi:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Disable WiFi power management
|
||||||
|
sudo iwconfig wlan0 power off
|
||||||
|
|
||||||
|
# Make permanent
|
||||||
|
echo 'wireless-power off' | sudo tee -a /etc/network/interfaces
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cost Comparison
|
||||||
|
|
||||||
|
| Setup | One-Time Cost | Monthly Cost | Notes |
|
||||||
|
|-------|---------------|--------------|-------|
|
||||||
|
| **Pi 4 (2GB)** | ~$45 | $0 | + power (~$5/yr) |
|
||||||
|
| **Pi 4 (4GB)** | ~$55 | $0 | Recommended |
|
||||||
|
| **Pi 5 (4GB)** | ~$60 | $0 | Best performance |
|
||||||
|
| **Pi 5 (8GB)** | ~$80 | $0 | Overkill but future-proof |
|
||||||
|
| DigitalOcean | $0 | $6/mo | $72/year |
|
||||||
|
| Hetzner | $0 | €3.79/mo | ~$50/year |
|
||||||
|
|
||||||
|
**Break-even:** A Pi pays for itself in ~6-12 months vs cloud VPS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Linux guide](/platforms/linux) — general Linux setup
|
||||||
|
- [DigitalOcean guide](/platforms/digitalocean) — cloud alternative
|
||||||
|
- [Hetzner guide](/platforms/hetzner) — Docker setup
|
||||||
|
- [Tailscale](/gateway/tailscale) — remote access
|
||||||
|
- [Nodes](/nodes) — pair your laptop/phone with the Pi gateway
|
||||||
@@ -7,7 +7,8 @@ read_when:
|
|||||||
# Windows (WSL2)
|
# Windows (WSL2)
|
||||||
|
|
||||||
Clawdbot on Windows is recommended **via WSL2** (Ubuntu recommended). The
|
Clawdbot on Windows is recommended **via WSL2** (Ubuntu recommended). The
|
||||||
CLI + Gateway run inside Linux, which keeps the runtime consistent. Native
|
CLI + Gateway run inside Linux, which keeps the runtime consistent and makes
|
||||||
|
tooling far more compatible (Node/Bun/pnpm, Linux binaries, skills). Native
|
||||||
Windows installs are untested and more problematic.
|
Windows installs are untested and more problematic.
|
||||||
|
|
||||||
Native Windows companion apps are planned.
|
Native Windows companion apps are planned.
|
||||||
|
|||||||
@@ -67,6 +67,22 @@ Plugins can register:
|
|||||||
Plugins run **in‑process** with the Gateway, so treat them as trusted code.
|
Plugins run **in‑process** with the Gateway, so treat them as trusted code.
|
||||||
Tool authoring guide: [Plugin agent tools](/plugins/agent-tools).
|
Tool authoring guide: [Plugin agent tools](/plugins/agent-tools).
|
||||||
|
|
||||||
|
## Runtime helpers
|
||||||
|
|
||||||
|
Plugins can access selected core helpers via `api.runtime`. For telephony TTS:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const result = await api.runtime.tts.textToSpeechTelephony({
|
||||||
|
text: "Hello from Clawdbot",
|
||||||
|
cfg: api.config,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- Uses core `messages.tts` configuration (OpenAI or ElevenLabs).
|
||||||
|
- Returns PCM audio buffer + sample rate. Plugins must resample/encode for providers.
|
||||||
|
- Edge TTS is not supported for telephony.
|
||||||
|
|
||||||
## Discovery & precedence
|
## Discovery & precedence
|
||||||
|
|
||||||
Clawdbot scans, in order:
|
Clawdbot scans, in order:
|
||||||
|
|||||||
@@ -103,6 +103,89 @@ Notes:
|
|||||||
- Plivo requires a **publicly reachable** webhook URL.
|
- Plivo requires a **publicly reachable** webhook URL.
|
||||||
- `mock` is a local dev provider (no network calls).
|
- `mock` is a local dev provider (no network calls).
|
||||||
- `skipSignatureVerification` is for local testing only.
|
- `skipSignatureVerification` is for local testing only.
|
||||||
|
- If you use ngrok free tier, set `publicUrl` to the exact ngrok URL; signature verification is always enforced.
|
||||||
|
- Ngrok free tier URLs can change or add interstitial behavior; if `publicUrl` drifts, Twilio signatures will fail. For production, prefer a stable domain or Tailscale funnel.
|
||||||
|
|
||||||
|
## TTS for calls
|
||||||
|
|
||||||
|
Voice Call uses the core `messages.tts` configuration (OpenAI or ElevenLabs) for
|
||||||
|
streaming speech on calls. You can override it under the plugin config with the
|
||||||
|
**same shape** — it deep‑merges with `messages.tts`.
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
tts: {
|
||||||
|
provider: "elevenlabs",
|
||||||
|
elevenlabs: {
|
||||||
|
voiceId: "pMsXgVXv3BLzUgSXRplE",
|
||||||
|
modelId: "eleven_multilingual_v2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- **Edge TTS is ignored for voice calls** (telephony audio needs PCM; Edge output is unreliable).
|
||||||
|
- Core TTS is used when Twilio media streaming is enabled; otherwise calls fall back to provider native voices.
|
||||||
|
|
||||||
|
### More examples
|
||||||
|
|
||||||
|
Use core TTS only (no override):
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
messages: {
|
||||||
|
tts: {
|
||||||
|
provider: "openai",
|
||||||
|
openai: { voice: "alloy" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Override to ElevenLabs just for calls (keep core default elsewhere):
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
entries: {
|
||||||
|
"voice-call": {
|
||||||
|
config: {
|
||||||
|
tts: {
|
||||||
|
provider: "elevenlabs",
|
||||||
|
elevenlabs: {
|
||||||
|
apiKey: "elevenlabs_key",
|
||||||
|
voiceId: "pMsXgVXv3BLzUgSXRplE",
|
||||||
|
modelId: "eleven_multilingual_v2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Override only the OpenAI model for calls (deep‑merge example):
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
entries: {
|
||||||
|
"voice-call": {
|
||||||
|
config: {
|
||||||
|
tts: {
|
||||||
|
openai: {
|
||||||
|
model: "gpt-4o-mini-tts",
|
||||||
|
voice: "marin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Inbound calls
|
## Inbound calls
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,11 @@ clawdbot onboard --auth-choice claude-cli
|
|||||||
- If the Claude CLI login lives on a different machine, use
|
- If the Claude CLI login lives on a different machine, use
|
||||||
`clawdbot models auth paste-token --provider anthropic` on the gateway host.
|
`clawdbot models auth paste-token --provider anthropic` on the gateway host.
|
||||||
|
|
||||||
|
**No API key found for provider "anthropic"**
|
||||||
|
- Auth is **per agent**. New agents don’t inherit the main agent’s keys.
|
||||||
|
- Re-run onboarding for that agent, or paste a setup-token / API key on the
|
||||||
|
gateway host, then verify with `clawdbot models status`.
|
||||||
|
|
||||||
**No credentials found for profile `anthropic:default` or `anthropic:claude-cli`**
|
**No credentials found for profile `anthropic:default` or `anthropic:claude-cli`**
|
||||||
- Run `clawdbot models status` to see which auth profile is active.
|
- Run `clawdbot models status` to see which auth profile is active.
|
||||||
- Re-run onboarding, or paste a setup-token / API key for that profile.
|
- Re-run onboarding, or paste a setup-token / API key for that profile.
|
||||||
|
|||||||
145
docs/providers/claude-max-api-proxy.md
Normal file
145
docs/providers/claude-max-api-proxy.md
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
---
|
||||||
|
summary: "Use Claude Max/Pro subscription as an OpenAI-compatible API endpoint"
|
||||||
|
read_when:
|
||||||
|
- You want to use Claude Max subscription with OpenAI-compatible tools
|
||||||
|
- You want a local API server that wraps Claude Code CLI
|
||||||
|
- You want to save money by using subscription instead of API keys
|
||||||
|
---
|
||||||
|
# Claude Max API Proxy
|
||||||
|
|
||||||
|
**claude-max-api-proxy** is a community tool that exposes your Claude Max/Pro subscription as an OpenAI-compatible API endpoint. This allows you to use your subscription with any tool that supports the OpenAI API format.
|
||||||
|
|
||||||
|
## Why Use This?
|
||||||
|
|
||||||
|
| Approach | Cost | Best For |
|
||||||
|
|----------|------|----------|
|
||||||
|
| Anthropic API | Pay per token (~$15/M input, $75/M output for Opus) | Production apps, high volume |
|
||||||
|
| Claude Max subscription | $200/month flat | Personal use, development, unlimited usage |
|
||||||
|
|
||||||
|
If you have a Claude Max subscription and want to use it with OpenAI-compatible tools, this proxy can save you significant money.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
```
|
||||||
|
Your App → claude-max-api-proxy → Claude Code CLI → Anthropic (via subscription)
|
||||||
|
(OpenAI format) (converts format) (uses your login)
|
||||||
|
```
|
||||||
|
|
||||||
|
The proxy:
|
||||||
|
1. Accepts OpenAI-format requests at `http://localhost:3456/v1/chat/completions`
|
||||||
|
2. Converts them to Claude Code CLI commands
|
||||||
|
3. Returns responses in OpenAI format (streaming supported)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Requires Node.js 20+ and Claude Code CLI
|
||||||
|
npm install -g claude-max-api-proxy
|
||||||
|
|
||||||
|
# Verify Claude CLI is authenticated
|
||||||
|
claude --version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Start the server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude-max-api
|
||||||
|
# Server runs at http://localhost:3456
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test it
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Health check
|
||||||
|
curl http://localhost:3456/health
|
||||||
|
|
||||||
|
# List models
|
||||||
|
curl http://localhost:3456/v1/models
|
||||||
|
|
||||||
|
# Chat completion
|
||||||
|
curl http://localhost:3456/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"model": "claude-opus-4",
|
||||||
|
"messages": [{"role": "user", "content": "Hello!"}]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Clawdbot
|
||||||
|
|
||||||
|
You can point Clawdbot at the proxy as a custom OpenAI-compatible endpoint:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
env: {
|
||||||
|
OPENAI_API_KEY: "not-needed",
|
||||||
|
OPENAI_BASE_URL: "http://localhost:3456/v1"
|
||||||
|
},
|
||||||
|
agents: {
|
||||||
|
defaults: {
|
||||||
|
model: { primary: "openai/claude-opus-4" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Models
|
||||||
|
|
||||||
|
| Model ID | Maps To |
|
||||||
|
|----------|---------|
|
||||||
|
| `claude-opus-4` | Claude Opus 4 |
|
||||||
|
| `claude-sonnet-4` | Claude Sonnet 4 |
|
||||||
|
| `claude-haiku-4` | Claude Haiku 4 |
|
||||||
|
|
||||||
|
## Auto-Start on macOS
|
||||||
|
|
||||||
|
Create a LaunchAgent to run the proxy automatically:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat > ~/Library/LaunchAgents/com.claude-max-api.plist << 'EOF'
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>Label</key>
|
||||||
|
<string>com.claude-max-api</string>
|
||||||
|
<key>RunAtLoad</key>
|
||||||
|
<true/>
|
||||||
|
<key>KeepAlive</key>
|
||||||
|
<true/>
|
||||||
|
<key>ProgramArguments</key>
|
||||||
|
<array>
|
||||||
|
<string>/usr/local/bin/node</string>
|
||||||
|
<string>/usr/local/lib/node_modules/claude-max-api-proxy/dist/server/standalone.js</string>
|
||||||
|
</array>
|
||||||
|
<key>EnvironmentVariables</key>
|
||||||
|
<dict>
|
||||||
|
<key>PATH</key>
|
||||||
|
<string>/usr/local/bin:/opt/homebrew/bin:~/.local/bin:/usr/bin:/bin</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.claude-max-api.plist
|
||||||
|
```
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- **npm:** https://www.npmjs.com/package/claude-max-api-proxy
|
||||||
|
- **GitHub:** https://github.com/atalovesyou/claude-max-api-proxy
|
||||||
|
- **Issues:** https://github.com/atalovesyou/claude-max-api-proxy/issues
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- This is a **community tool**, not officially supported by Anthropic or Clawdbot
|
||||||
|
- Requires an active Claude Max/Pro subscription with Claude Code CLI authenticated
|
||||||
|
- The proxy runs locally and does not send data to any third-party servers
|
||||||
|
- Streaming responses are fully supported
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [Anthropic provider](/providers/anthropic) - Native Clawdbot integration with Claude Code CLI OAuth
|
||||||
|
- [OpenAI provider](/providers/openai) - For OpenAI/Codex subscriptions
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user