Merge branch 'main' into main
This commit is contained in:
commit
ff8b6d86c8
174
.github/labeler.yml
vendored
Normal file
174
.github/labeler.yml
vendored
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
"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/**"
|
||||||
|
"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"
|
||||||
|
|
||||||
|
"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",
|
||||||
|
});
|
||||||
|
}
|
||||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -342,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:
|
||||||
|
|||||||
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 }}
|
||||||
@ -13,6 +13,7 @@
|
|||||||
- 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).
|
||||||
|
|||||||
24
CHANGELOG.md
24
CHANGELOG.md
@ -2,11 +2,35 @@
|
|||||||
|
|
||||||
Docs: https://docs.clawd.bot
|
Docs: https://docs.clawd.bot
|
||||||
|
|
||||||
|
## 2026.1.25
|
||||||
|
Status: unreleased.
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Doctor: warn on gateway exposure without auth. (#2016) Thanks @Alex-Alaniz.
|
||||||
|
- 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.
|
||||||
|
- 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: 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.
|
||||||
|
|
||||||
## 2026.1.24-3
|
## 2026.1.24-3
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Gateway: harden reverse proxy handling for local-client detection and unauthenticated proxied connects. (#1795) Thanks @orlyjamie.
|
- 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.
|
- 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
|
## 2026.1.24-2
|
||||||
|
|
||||||
|
|||||||
55
README.md
55
README.md
@ -479,31 +479,32 @@ 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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=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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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=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/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/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/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/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/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/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/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/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/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/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/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=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/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/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=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/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/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/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/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/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/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/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/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/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/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/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/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/search?q=Azade"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Azade" title="Azade"/></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>
|
<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/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>
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -827,6 +827,7 @@
|
|||||||
"install/nix",
|
"install/nix",
|
||||||
"install/docker",
|
"install/docker",
|
||||||
"railway",
|
"railway",
|
||||||
|
"render",
|
||||||
"install/bun"
|
"install/bun"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -983,6 +984,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",
|
||||||
|
|||||||
@ -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"`
|
||||||
|
|||||||
@ -182,7 +182,7 @@ cat > /data/clawdbot.json << 'EOF'
|
|||||||
"bind": "auto"
|
"bind": "auto"
|
||||||
},
|
},
|
||||||
"meta": {
|
"meta": {
|
||||||
"lastTouchedVersion": "2026.1.24"
|
"lastTouchedVersion": "2026.1.25"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@ -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-3 \
|
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-3.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-3.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-3.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-3 \
|
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-3.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-3.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-3.zip` (and `Clawdbot-2026.1.24-3.dSYM.zip`) to the GitHub release for tag `v2026.1.24-3`.
|
- 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.
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
title: "Vercel AI Gateway"
|
||||||
summary: "Vercel AI Gateway setup (auth + model selection)"
|
summary: "Vercel AI Gateway setup (auth + model selection)"
|
||||||
read_when:
|
read_when:
|
||||||
- You want to use Vercel AI Gateway with Clawdbot
|
- You want to use Vercel AI Gateway with Clawdbot
|
||||||
|
|||||||
@ -16,7 +16,7 @@ and you configure everything via the `/setup` web wizard.
|
|||||||
|
|
||||||
## One-click deploy
|
## One-click deploy
|
||||||
|
|
||||||
<a href="https://railway.app/new/template?template=https://github.com/vignesh07/clawdbot-railway-template" target="_blank" rel="noreferrer">Deploy on Railway</a>
|
<a href="https://railway.com/deploy/clawdbot-railway-template" target="_blank" rel="noreferrer">Deploy on Railway</a>
|
||||||
|
|
||||||
After deploy, find your public URL in **Railway → your service → Settings → Domains**.
|
After deploy, find your public URL in **Railway → your service → Settings → Domains**.
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ When the operator says “release”, immediately do this preflight (no extra qu
|
|||||||
- Use Sparkle keys from `~/Library/CloudStorage/Dropbox/Backup/Sparkle` if needed.
|
- Use Sparkle keys from `~/Library/CloudStorage/Dropbox/Backup/Sparkle` if needed.
|
||||||
|
|
||||||
1) **Version & metadata**
|
1) **Version & metadata**
|
||||||
- [ ] Bump `package.json` version (e.g., `2026.1.24`).
|
- [ ] Bump `package.json` version (e.g., `2026.1.25`).
|
||||||
- [ ] Run `pnpm plugins:sync` to align extension package versions + changelogs.
|
- [ ] Run `pnpm plugins:sync` to align extension package versions + changelogs.
|
||||||
- [ ] Update CLI/version strings: [`src/cli/program.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/cli/program.ts) and the Baileys user agent in [`src/provider-web.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/provider-web.ts).
|
- [ ] Update CLI/version strings: [`src/cli/program.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/cli/program.ts) and the Baileys user agent in [`src/provider-web.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/provider-web.ts).
|
||||||
- [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to [`dist/entry.js`](https://github.com/clawdbot/clawdbot/blob/main/dist/entry.js) for `clawdbot`.
|
- [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to [`dist/entry.js`](https://github.com/clawdbot/clawdbot/blob/main/dist/entry.js) for `clawdbot`.
|
||||||
|
|||||||
158
docs/render.mdx
Normal file
158
docs/render.mdx
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
---
|
||||||
|
title: Deploy on Render
|
||||||
|
---
|
||||||
|
|
||||||
|
Deploy Clawdbot on Render using Infrastructure as Code. The included `render.yaml` Blueprint defines your entire stack declaratively, service, disk, environment variables, so you can deploy with a single click and version your infrastructure alongside your code.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- A [Render account](https://render.com) (free tier available)
|
||||||
|
- An API key from your preferred [model provider](/providers)
|
||||||
|
|
||||||
|
## Deploy with a Render Blueprint
|
||||||
|
|
||||||
|
<a href="https://render.com/deploy?repo=https://github.com/clawdbot/clawdbot" target="_blank" rel="noreferrer">Deploy to Render</a>
|
||||||
|
|
||||||
|
Clicking this link will:
|
||||||
|
|
||||||
|
1. Create a new Render service from the `render.yaml` Blueprint at the root of this repo.
|
||||||
|
2. Prompt you to set `SETUP_PASSWORD`
|
||||||
|
3. Build the Docker image and deploy
|
||||||
|
|
||||||
|
Once deployed, your service URL follows the pattern `https://<service-name>.onrender.com`.
|
||||||
|
|
||||||
|
## Understanding the Blueprint
|
||||||
|
|
||||||
|
Render Blueprints are YAML files that define your infrastructure. The `render.yaml` in this
|
||||||
|
repository configures everything needed to run Clawdbot:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
- type: web
|
||||||
|
name: clawdbot
|
||||||
|
runtime: docker
|
||||||
|
plan: starter
|
||||||
|
healthCheckPath: /health
|
||||||
|
envVars:
|
||||||
|
- key: PORT
|
||||||
|
value: "8080"
|
||||||
|
- key: SETUP_PASSWORD
|
||||||
|
sync: false # prompts during deploy
|
||||||
|
- key: CLAWDBOT_STATE_DIR
|
||||||
|
value: /data/.clawdbot
|
||||||
|
- key: CLAWDBOT_WORKSPACE_DIR
|
||||||
|
value: /data/workspace
|
||||||
|
- key: CLAWDBOT_GATEWAY_TOKEN
|
||||||
|
generateValue: true # auto-generates a secure token
|
||||||
|
disk:
|
||||||
|
name: clawdbot-data
|
||||||
|
mountPath: /data
|
||||||
|
sizeGB: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Key Blueprint features used:
|
||||||
|
|
||||||
|
| Feature | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| `runtime: docker` | Builds from the repo's Dockerfile |
|
||||||
|
| `healthCheckPath` | Render monitors `/health` and restarts unhealthy instances |
|
||||||
|
| `sync: false` | Prompts for value during deploy (secrets) |
|
||||||
|
| `generateValue: true` | Auto-generates a cryptographically secure value |
|
||||||
|
| `disk` | Persistent storage that survives redeploys |
|
||||||
|
|
||||||
|
## Choosing a plan
|
||||||
|
|
||||||
|
| Plan | Spin-down | Disk | Best for |
|
||||||
|
|------|-----------|------|----------|
|
||||||
|
| Free | After 15 min idle | Not available | Testing, demos |
|
||||||
|
| Starter | Never | 1GB+ | Personal use, small teams |
|
||||||
|
| Standard+ | Never | 1GB+ | Production, multiple channels |
|
||||||
|
|
||||||
|
The Blueprint defaults to `starter`. To use free tier, change `plan: free` in your fork's
|
||||||
|
`render.yaml` (but note: no persistent disk means config resets on each deploy).
|
||||||
|
|
||||||
|
## After deployment
|
||||||
|
|
||||||
|
### Complete the setup wizard
|
||||||
|
|
||||||
|
1. Navigate to `https://<your-service>.onrender.com/setup`
|
||||||
|
2. Enter your `SETUP_PASSWORD`
|
||||||
|
3. Select a model provider and paste your API key
|
||||||
|
4. Optionally configure messaging channels (Telegram, Discord, Slack)
|
||||||
|
5. Click **Run setup**
|
||||||
|
|
||||||
|
### Access the Control UI
|
||||||
|
|
||||||
|
The web dashboard is available at `https://<your-service>.onrender.com/clawdbot`.
|
||||||
|
|
||||||
|
## Render Dashboard features
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
View real-time logs in **Dashboard → your service → Logs**. Filter by:
|
||||||
|
- Build logs (Docker image creation)
|
||||||
|
- Deploy logs (service startup)
|
||||||
|
- Runtime logs (application output)
|
||||||
|
|
||||||
|
### Shell access
|
||||||
|
|
||||||
|
For debugging, open a shell session via **Dashboard → your service → Shell**. The persistent disk is mounted at `/data`.
|
||||||
|
|
||||||
|
### Environment variables
|
||||||
|
|
||||||
|
Modify variables in **Dashboard → your service → Environment**. Changes trigger an automatic redeploy.
|
||||||
|
|
||||||
|
### Auto-deploy
|
||||||
|
|
||||||
|
If you use the original Clawdbot repository, Render will not auto-deploy your Clawdbot. To update it, run a manual Blueprint sync from the dashboard.
|
||||||
|
|
||||||
|
## Custom domain
|
||||||
|
|
||||||
|
1. Go to **Dashboard → your service → Settings → Custom Domains**
|
||||||
|
2. Add your domain
|
||||||
|
3. Configure DNS as instructed (CNAME to `*.onrender.com`)
|
||||||
|
4. Render provisions a TLS certificate automatically
|
||||||
|
|
||||||
|
## Scaling
|
||||||
|
|
||||||
|
Render supports horizontal and vertical scaling:
|
||||||
|
|
||||||
|
- **Vertical**: Change the plan to get more CPU/RAM
|
||||||
|
- **Horizontal**: Increase instance count (Standard plan and above)
|
||||||
|
|
||||||
|
For Clawdbot, vertical scaling is usually sufficient. Horizontal scaling requires sticky sessions or external state management.
|
||||||
|
|
||||||
|
## Backups and migration
|
||||||
|
|
||||||
|
Export your configuration and workspace at any time:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://<your-service>.onrender.com/setup/export
|
||||||
|
```
|
||||||
|
|
||||||
|
This downloads a portable backup you can restore on any Clawdbot host.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Service won't start
|
||||||
|
|
||||||
|
Check the deploy logs in the Render Dashboard. Common issues:
|
||||||
|
|
||||||
|
- Missing `SETUP_PASSWORD` — the Blueprint prompts for this, but verify it's set
|
||||||
|
- Port mismatch — ensure `PORT=8080` matches the Dockerfile's exposed port
|
||||||
|
|
||||||
|
### Slow cold starts (free tier)
|
||||||
|
|
||||||
|
Free tier services spin down after 15 minutes of inactivity. The first request after spin-down takes a few seconds while the container starts. Upgrade to Starter plan for always-on.
|
||||||
|
|
||||||
|
### Data loss after redeploy
|
||||||
|
|
||||||
|
This happens on free tier (no persistent disk). Upgrade to a paid plan, or
|
||||||
|
regularly export your config via `/setup/export`.
|
||||||
|
|
||||||
|
### Health check failures
|
||||||
|
|
||||||
|
Render expects a 200 response from `/health` within 30 seconds. If builds succeed but deploys fail, the service may be taking too long to start. Check:
|
||||||
|
|
||||||
|
- Build logs for errors
|
||||||
|
- Whether the container runs locally with `docker build && docker run`
|
||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/bluebubbles",
|
"name": "@clawdbot/bluebubbles",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot BlueBubbles channel plugin",
|
"description": "Clawdbot BlueBubbles channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/copilot-proxy",
|
"name": "@clawdbot/copilot-proxy",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Copilot Proxy provider plugin",
|
"description": "Clawdbot Copilot Proxy provider plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/diagnostics-otel",
|
"name": "@clawdbot/diagnostics-otel",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot diagnostics OpenTelemetry exporter",
|
"description": "Clawdbot diagnostics OpenTelemetry exporter",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/discord",
|
"name": "@clawdbot/discord",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Discord channel plugin",
|
"description": "Clawdbot Discord channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -281,6 +281,7 @@ async function loginAntigravity(params: {
|
|||||||
openUrl: (url: string) => Promise<void>;
|
openUrl: (url: string) => Promise<void>;
|
||||||
prompt: (message: string) => Promise<string>;
|
prompt: (message: string) => Promise<string>;
|
||||||
note: (message: string, title?: string) => Promise<void>;
|
note: (message: string, title?: string) => Promise<void>;
|
||||||
|
log: (message: string) => void;
|
||||||
progress: { update: (msg: string) => void; stop: (msg?: string) => void };
|
progress: { update: (msg: string) => void; stop: (msg?: string) => void };
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
access: string;
|
access: string;
|
||||||
@ -314,6 +315,11 @@ async function loginAntigravity(params: {
|
|||||||
].join("\n"),
|
].join("\n"),
|
||||||
"Google Antigravity OAuth",
|
"Google Antigravity OAuth",
|
||||||
);
|
);
|
||||||
|
// Output raw URL below the box for easy copying (fixes #1772)
|
||||||
|
params.log("");
|
||||||
|
params.log("Copy this URL:");
|
||||||
|
params.log(authUrl);
|
||||||
|
params.log("");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!needsManual) {
|
if (!needsManual) {
|
||||||
@ -382,6 +388,7 @@ const antigravityPlugin = {
|
|||||||
openUrl: ctx.openUrl,
|
openUrl: ctx.openUrl,
|
||||||
prompt: async (message) => String(await ctx.prompter.text({ message })),
|
prompt: async (message) => String(await ctx.prompter.text({ message })),
|
||||||
note: ctx.prompter.note,
|
note: ctx.prompter.note,
|
||||||
|
log: (message) => ctx.runtime.log(message),
|
||||||
progress: spin,
|
progress: spin,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/google-antigravity-auth",
|
"name": "@clawdbot/google-antigravity-auth",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Google Antigravity OAuth provider plugin",
|
"description": "Clawdbot Google Antigravity OAuth provider plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/google-gemini-cli-auth",
|
"name": "@clawdbot/google-gemini-cli-auth",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Gemini CLI OAuth provider plugin",
|
"description": "Clawdbot Gemini CLI OAuth provider plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/googlechat",
|
"name": "@clawdbot/googlechat",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Google Chat channel plugin",
|
"description": "Clawdbot Google Chat channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
@ -34,6 +34,6 @@
|
|||||||
"clawdbot": "workspace:*"
|
"clawdbot": "workspace:*"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"clawdbot": ">=2026.1.24"
|
"clawdbot": ">=2026.1.25"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/imessage",
|
"name": "@clawdbot/imessage",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot iMessage channel plugin",
|
"description": "Clawdbot iMessage channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/line",
|
"name": "@clawdbot/line",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot LINE channel plugin",
|
"description": "Clawdbot LINE channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/llm-task",
|
"name": "@clawdbot/llm-task",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot JSON-only LLM task plugin",
|
"description": "Clawdbot JSON-only LLM task plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/lobster",
|
"name": "@clawdbot/lobster",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Lobster workflow tool plugin (typed pipelines + resumable approvals)",
|
"description": "Lobster workflow tool plugin (typed pipelines + resumable approvals)",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/matrix",
|
"name": "@clawdbot/matrix",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Matrix channel plugin",
|
"description": "Clawdbot Matrix channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/mattermost",
|
"name": "@clawdbot/mattermost",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Mattermost channel plugin",
|
"description": "Clawdbot Mattermost channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/memory-core",
|
"name": "@clawdbot/memory-core",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot core memory search plugin",
|
"description": "Clawdbot core memory search plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
@ -9,6 +9,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"clawdbot": ">=2026.1.24"
|
"clawdbot": ">=2026.1.25"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/memory-lancedb",
|
"name": "@clawdbot/memory-lancedb",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot LanceDB-backed long-term memory plugin with auto-recall/capture",
|
"description": "Clawdbot LanceDB-backed long-term memory plugin with auto-recall/capture",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/msteams",
|
"name": "@clawdbot/msteams",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Microsoft Teams channel plugin",
|
"description": "Clawdbot Microsoft Teams channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/nextcloud-talk",
|
"name": "@clawdbot/nextcloud-talk",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Nextcloud Talk channel plugin",
|
"description": "Clawdbot Nextcloud Talk channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/nostr",
|
"name": "@clawdbot/nostr",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Nostr channel plugin for NIP-04 encrypted DMs",
|
"description": "Clawdbot Nostr channel plugin for NIP-04 encrypted DMs",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/open-prose",
|
"name": "@clawdbot/open-prose",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "OpenProse VM skill pack plugin (slash command + telemetry).",
|
"description": "OpenProse VM skill pack plugin (slash command + telemetry).",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/signal",
|
"name": "@clawdbot/signal",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Signal channel plugin",
|
"description": "Clawdbot Signal channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/slack",
|
"name": "@clawdbot/slack",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Slack channel plugin",
|
"description": "Clawdbot Slack channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/telegram",
|
"name": "@clawdbot/telegram",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Telegram channel plugin",
|
"description": "Clawdbot Telegram channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/tlon",
|
"name": "@clawdbot/tlon",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Tlon/Urbit channel plugin",
|
"description": "Clawdbot Tlon/Urbit channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 2026.1.24
|
## 2026.1.25
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
- Breaking: voice-call TTS now uses core `messages.tts` (plugin TTS config deep‑merges with core).
|
- Breaking: voice-call TTS now uses core `messages.tts` (plugin TTS config deep‑merges with core).
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/voice-call",
|
"name": "@clawdbot/voice-call",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot voice-call plugin",
|
"description": "Clawdbot voice-call plugin",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/whatsapp",
|
"name": "@clawdbot/whatsapp",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot WhatsApp channel plugin",
|
"description": "Clawdbot WhatsApp channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/zalo",
|
"name": "@clawdbot/zalo",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Zalo channel plugin",
|
"description": "Clawdbot Zalo channel plugin",
|
||||||
"clawdbot": {
|
"clawdbot": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@clawdbot/zalouser",
|
"name": "@clawdbot/zalouser",
|
||||||
"version": "2026.1.24",
|
"version": "2026.1.25",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Clawdbot Zalo Personal Account plugin via zca-cli",
|
"description": "Clawdbot Zalo Personal Account plugin via zca-cli",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "clawdbot",
|
"name": "clawdbot",
|
||||||
"version": "2026.1.24-3",
|
"version": "2026.1.25",
|
||||||
"description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent",
|
"description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@ -357,7 +357,7 @@ importers:
|
|||||||
extensions/memory-core:
|
extensions/memory-core:
|
||||||
dependencies:
|
dependencies:
|
||||||
clawdbot:
|
clawdbot:
|
||||||
specifier: '>=2026.1.24'
|
specifier: '>=2026.1.25'
|
||||||
version: link:../..
|
version: link:../..
|
||||||
|
|
||||||
extensions/memory-lancedb:
|
extensions/memory-lancedb:
|
||||||
|
|||||||
21
render.yaml
Normal file
21
render.yaml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
services:
|
||||||
|
- type: web
|
||||||
|
name: clawdbot
|
||||||
|
runtime: docker
|
||||||
|
plan: starter
|
||||||
|
healthCheckPath: /health
|
||||||
|
envVars:
|
||||||
|
- key: PORT
|
||||||
|
value: "8080"
|
||||||
|
- key: SETUP_PASSWORD
|
||||||
|
sync: false
|
||||||
|
- key: CLAWDBOT_STATE_DIR
|
||||||
|
value: /data/.clawdbot
|
||||||
|
- key: CLAWDBOT_WORKSPACE_DIR
|
||||||
|
value: /data/workspace
|
||||||
|
- key: CLAWDBOT_GATEWAY_TOKEN
|
||||||
|
generateValue: true
|
||||||
|
disk:
|
||||||
|
name: clawdbot-data
|
||||||
|
mountPath: /data
|
||||||
|
sizeGB: 1
|
||||||
107
scripts/sync-labels.ts
Normal file
107
scripts/sync-labels.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { execFileSync } from "node:child_process";
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
|
||||||
|
type RepoLabel = {
|
||||||
|
name: string;
|
||||||
|
color?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const COLOR_BY_PREFIX = new Map<string, string>([
|
||||||
|
["channel", "1d76db"],
|
||||||
|
["app", "6f42c1"],
|
||||||
|
["extensions", "0e8a16"],
|
||||||
|
["docs", "0075ca"],
|
||||||
|
["cli", "f9d0c4"],
|
||||||
|
["gateway", "d4c5f9"],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const configPath = resolve(".github/labeler.yml");
|
||||||
|
const labelNames = extractLabelNames(readFileSync(configPath, "utf8"));
|
||||||
|
|
||||||
|
if (!labelNames.length) {
|
||||||
|
throw new Error("labeler.yml must declare at least one label.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const repo = resolveRepo();
|
||||||
|
const existing = fetchExistingLabels(repo);
|
||||||
|
|
||||||
|
const missing = labelNames.filter((label) => !existing.has(label));
|
||||||
|
if (!missing.length) {
|
||||||
|
console.log("All labeler labels already exist.");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const label of missing) {
|
||||||
|
const color = pickColor(label);
|
||||||
|
execFileSync(
|
||||||
|
"gh",
|
||||||
|
[
|
||||||
|
"api",
|
||||||
|
"-X",
|
||||||
|
"POST",
|
||||||
|
`repos/${repo}/labels`,
|
||||||
|
"-f",
|
||||||
|
`name=${label}`,
|
||||||
|
"-f",
|
||||||
|
`color=${color}`,
|
||||||
|
],
|
||||||
|
{ stdio: "inherit" },
|
||||||
|
);
|
||||||
|
console.log(`Created label: ${label}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractLabelNames(contents: string): string[] {
|
||||||
|
const labels: string[] = [];
|
||||||
|
for (const line of contents.split("\n")) {
|
||||||
|
if (!line.trim() || line.trimStart().startsWith("#")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (/^\s/.test(line)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const match = line.match(/^(["'])(.+)\1\s*:/) ?? line.match(/^([^:]+):/);
|
||||||
|
if (match) {
|
||||||
|
const name = (match[2] ?? match[1] ?? "").trim();
|
||||||
|
if (name) {
|
||||||
|
labels.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickColor(label: string): string {
|
||||||
|
const prefix = label.includes(":") ? label.split(":", 1)[0].trim() : label.trim();
|
||||||
|
return COLOR_BY_PREFIX.get(prefix) ?? "ededed";
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveRepo(): string {
|
||||||
|
const remote = execFileSync("git", ["config", "--get", "remote.origin.url"], {
|
||||||
|
encoding: "utf8",
|
||||||
|
}).trim();
|
||||||
|
|
||||||
|
if (!remote) {
|
||||||
|
throw new Error("Unable to determine repository from git remote.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remote.startsWith("git@github.com:")) {
|
||||||
|
return remote.replace("git@github.com:", "").replace(/\.git$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remote.startsWith("https://github.com/")) {
|
||||||
|
return remote.replace("https://github.com/", "").replace(/\.git$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unsupported GitHub remote: ${remote}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchExistingLabels(repo: string): Map<string, RepoLabel> {
|
||||||
|
const raw = execFileSync(
|
||||||
|
"gh",
|
||||||
|
["api", `repos/${repo}/labels?per_page=100`, "--paginate"],
|
||||||
|
{ encoding: "utf8" },
|
||||||
|
);
|
||||||
|
const labels = JSON.parse(raw) as RepoLabel[];
|
||||||
|
return new Map(labels.map((label) => [label.name, label]));
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: discord
|
name: discord
|
||||||
description: Use when you need to control Discord from Clawdbot via the discord tool: send messages, react, post or upload stickers, upload emojis, run polls, manage threads/pins/search, create/edit/delete channels and categories, fetch permissions or member/role/channel info, or handle moderation actions in Discord DMs or channels.
|
description: Use when you need to control Discord from Clawdbot via the discord tool: send messages, react, post or upload stickers, upload emojis, run polls, manage threads/pins/search, create/edit/delete channels and categories, fetch permissions or member/role/channel info, or handle moderation actions in Discord DMs or channels.
|
||||||
|
metadata: {"clawdbot":{"emoji":"🎮","requires":{"config":["channels.discord"]}}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Discord Actions
|
# Discord Actions
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: github
|
name: github
|
||||||
description: "Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries."
|
description: "Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries."
|
||||||
|
metadata: {"clawdbot":{"emoji":"🐙","requires":{"bins":["gh"]},"install":[{"id":"brew","kind":"brew","formula":"gh","bins":["gh"],"label":"Install GitHub CLI (brew)"},{"id":"apt","kind":"apt","package":"gh","bins":["gh"],"label":"Install GitHub CLI (apt)"}]}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# GitHub Skill
|
# GitHub Skill
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
name: notion
|
name: notion
|
||||||
description: Notion API for creating and managing pages, databases, and blocks.
|
description: Notion API for creating and managing pages, databases, and blocks.
|
||||||
homepage: https://developers.notion.com
|
homepage: https://developers.notion.com
|
||||||
metadata: {"clawdbot":{"emoji":"📝"}}
|
metadata: {"clawdbot":{"emoji":"📝","requires":{"env":["NOTION_API_KEY"]},"primaryEnv":"NOTION_API_KEY"}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# notion
|
# notion
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: slack
|
name: slack
|
||||||
description: Use when you need to control Slack from Clawdbot via the slack tool, including reacting to messages or pinning/unpinning items in Slack channels or DMs.
|
description: Use when you need to control Slack from Clawdbot via the slack tool, including reacting to messages or pinning/unpinning items in Slack channels or DMs.
|
||||||
|
metadata: {"clawdbot":{"emoji":"💬","requires":{"config":["channels.slack"]}}}
|
||||||
---
|
---
|
||||||
|
|
||||||
# Slack Actions
|
# Slack Actions
|
||||||
|
|||||||
@ -61,7 +61,7 @@ describe("runClaudeCliAgent", () => {
|
|||||||
expect(argv).toContain("hi");
|
expect(argv).toContain("hi");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses provided --session-id when a claude session id is provided", async () => {
|
it("uses --resume when a claude session id is provided", async () => {
|
||||||
runCommandWithTimeoutMock.mockResolvedValueOnce({
|
runCommandWithTimeoutMock.mockResolvedValueOnce({
|
||||||
stdout: JSON.stringify({ message: "ok", session_id: "sid-2" }),
|
stdout: JSON.stringify({ message: "ok", session_id: "sid-2" }),
|
||||||
stderr: "",
|
stderr: "",
|
||||||
@ -83,7 +83,7 @@ describe("runClaudeCliAgent", () => {
|
|||||||
|
|
||||||
expect(runCommandWithTimeoutMock).toHaveBeenCalledTimes(1);
|
expect(runCommandWithTimeoutMock).toHaveBeenCalledTimes(1);
|
||||||
const argv = runCommandWithTimeoutMock.mock.calls[0]?.[0] as string[];
|
const argv = runCommandWithTimeoutMock.mock.calls[0]?.[0] as string[];
|
||||||
expect(argv).toContain("--session-id");
|
expect(argv).toContain("--resume");
|
||||||
expect(argv).toContain("c9d7b831-1c31-4d22-80b9-1e50ca207d4b");
|
expect(argv).toContain("c9d7b831-1c31-4d22-80b9-1e50ca207d4b");
|
||||||
expect(argv).toContain("hi");
|
expect(argv).toContain("hi");
|
||||||
});
|
});
|
||||||
|
|||||||
@ -28,6 +28,14 @@ const CLAUDE_MODEL_ALIASES: Record<string, string> = {
|
|||||||
const DEFAULT_CLAUDE_BACKEND: CliBackendConfig = {
|
const DEFAULT_CLAUDE_BACKEND: CliBackendConfig = {
|
||||||
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}",
|
||||||
|
],
|
||||||
output: "json",
|
output: "json",
|
||||||
input: "arg",
|
input: "arg",
|
||||||
modelArg: "--model",
|
modelArg: "--model",
|
||||||
|
|||||||
@ -133,8 +133,50 @@ export function createCronTool(opts?: CronToolOptions): AnyAgentTool {
|
|||||||
return {
|
return {
|
||||||
label: "Cron",
|
label: "Cron",
|
||||||
name: "cron",
|
name: "cron",
|
||||||
description:
|
description: `Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events.
|
||||||
"Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events. Use `jobId` as the canonical identifier; `id` is accepted for compatibility. Use `contextMessages` (0-10) to add previous messages as context to the job text.",
|
|
||||||
|
ACTIONS:
|
||||||
|
- status: Check cron scheduler status
|
||||||
|
- list: List jobs (use includeDisabled:true to include disabled)
|
||||||
|
- add: Create job (requires job object, see schema below)
|
||||||
|
- update: Modify job (requires jobId + patch object)
|
||||||
|
- remove: Delete job (requires jobId)
|
||||||
|
- run: Trigger job immediately (requires jobId)
|
||||||
|
- runs: Get job run history (requires jobId)
|
||||||
|
- wake: Send wake event (requires text, optional mode)
|
||||||
|
|
||||||
|
JOB SCHEMA (for add action):
|
||||||
|
{
|
||||||
|
"name": "string (optional)",
|
||||||
|
"schedule": { ... }, // Required: when to run
|
||||||
|
"payload": { ... }, // Required: what to execute
|
||||||
|
"sessionTarget": "main" | "isolated", // Required
|
||||||
|
"enabled": true | false // Optional, default true
|
||||||
|
}
|
||||||
|
|
||||||
|
SCHEDULE TYPES (schedule.kind):
|
||||||
|
- "at": One-shot at absolute time
|
||||||
|
{ "kind": "at", "atMs": <unix-ms-timestamp> }
|
||||||
|
- "every": Recurring interval
|
||||||
|
{ "kind": "every", "everyMs": <interval-ms>, "anchorMs": <optional-start-ms> }
|
||||||
|
- "cron": Cron expression
|
||||||
|
{ "kind": "cron", "expr": "<cron-expression>", "tz": "<optional-timezone>" }
|
||||||
|
|
||||||
|
PAYLOAD TYPES (payload.kind):
|
||||||
|
- "systemEvent": Injects text as system event into session
|
||||||
|
{ "kind": "systemEvent", "text": "<message>" }
|
||||||
|
- "agentTurn": Runs agent with message (isolated sessions only)
|
||||||
|
{ "kind": "agentTurn", "message": "<prompt>", "model": "<optional>", "thinking": "<optional>", "timeoutSeconds": <optional>, "deliver": <optional-bool>, "channel": "<optional>", "to": "<optional>", "bestEffortDeliver": <optional-bool> }
|
||||||
|
|
||||||
|
CRITICAL CONSTRAINTS:
|
||||||
|
- sessionTarget="main" REQUIRES payload.kind="systemEvent"
|
||||||
|
- sessionTarget="isolated" REQUIRES payload.kind="agentTurn"
|
||||||
|
|
||||||
|
WAKE MODES (for wake action):
|
||||||
|
- "next-heartbeat" (default): Wake on next heartbeat
|
||||||
|
- "now": Wake immediately
|
||||||
|
|
||||||
|
Use jobId as the canonical identifier; id is accepted for compatibility. Use contextMessages (0-10) to add previous messages as context to the job text.`,
|
||||||
parameters: CronToolSchema,
|
parameters: CronToolSchema,
|
||||||
execute: async (_toolCallId, args) => {
|
execute: async (_toolCallId, args) => {
|
||||||
const params = args as Record<string, unknown>;
|
const params = args as Record<string, unknown>;
|
||||||
|
|||||||
@ -14,6 +14,7 @@ export type AnnounceTarget = {
|
|||||||
channel: string;
|
channel: string;
|
||||||
to: string;
|
to: string;
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
|
threadId?: string; // Forum topic/thread ID
|
||||||
};
|
};
|
||||||
|
|
||||||
export function resolveAnnounceTargetFromKey(sessionKey: string): AnnounceTarget | null {
|
export function resolveAnnounceTargetFromKey(sessionKey: string): AnnounceTarget | null {
|
||||||
@ -22,7 +23,22 @@ export function resolveAnnounceTargetFromKey(sessionKey: string): AnnounceTarget
|
|||||||
if (parts.length < 3) return null;
|
if (parts.length < 3) return null;
|
||||||
const [channelRaw, kind, ...rest] = parts;
|
const [channelRaw, kind, ...rest] = parts;
|
||||||
if (kind !== "group" && kind !== "channel") return null;
|
if (kind !== "group" && kind !== "channel") return null;
|
||||||
const id = rest.join(":").trim();
|
|
||||||
|
// Extract topic/thread ID from rest (supports both :topic: and :thread:)
|
||||||
|
// Telegram uses :topic:, other platforms use :thread:
|
||||||
|
let threadId: string | undefined;
|
||||||
|
const restJoined = rest.join(":");
|
||||||
|
const topicMatch = restJoined.match(/:topic:(\d+)$/);
|
||||||
|
const threadMatch = restJoined.match(/:thread:(\d+)$/);
|
||||||
|
const match = topicMatch || threadMatch;
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
threadId = match[1]; // Keep as string to match AgentCommandOpts.threadId
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove :topic:N or :thread:N suffix from ID for target
|
||||||
|
const id = match ? restJoined.replace(/:(topic|thread):\d+$/, "") : restJoined.trim();
|
||||||
|
|
||||||
if (!id) return null;
|
if (!id) return null;
|
||||||
if (!channelRaw) return null;
|
if (!channelRaw) return null;
|
||||||
const normalizedChannel = normalizeAnyChannelId(channelRaw) ?? normalizeChatChannelId(channelRaw);
|
const normalizedChannel = normalizeAnyChannelId(channelRaw) ?? normalizeChatChannelId(channelRaw);
|
||||||
@ -37,7 +53,11 @@ export function resolveAnnounceTargetFromKey(sessionKey: string): AnnounceTarget
|
|||||||
const normalized = normalizedChannel
|
const normalized = normalizedChannel
|
||||||
? getChannelPlugin(normalizedChannel)?.messaging?.normalizeTarget?.(kindTarget)
|
? getChannelPlugin(normalizedChannel)?.messaging?.normalizeTarget?.(kindTarget)
|
||||||
: undefined;
|
: undefined;
|
||||||
return { channel, to: normalized ?? kindTarget };
|
return {
|
||||||
|
channel,
|
||||||
|
to: normalized ?? kindTarget,
|
||||||
|
threadId,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildAgentToAgentMessageContext(params: {
|
export function buildAgentToAgentMessageContext(params: {
|
||||||
|
|||||||
@ -179,6 +179,17 @@ export async function runAgentTurnWithFallback(params: {
|
|||||||
images: params.opts?.images,
|
images: params.opts?.images,
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
// CLI backends don't emit streaming assistant events, so we need to
|
||||||
|
// emit one with the final text so server-chat can populate its buffer
|
||||||
|
// and send the response to TUI/WebSocket clients.
|
||||||
|
const cliText = result.payloads?.[0]?.text?.trim();
|
||||||
|
if (cliText) {
|
||||||
|
emitAgentEvent({
|
||||||
|
runId,
|
||||||
|
stream: "assistant",
|
||||||
|
data: { text: cliText },
|
||||||
|
});
|
||||||
|
}
|
||||||
emitAgentEvent({
|
emitAgentEvent({
|
||||||
runId,
|
runId,
|
||||||
stream: "lifecycle",
|
stream: "lifecycle",
|
||||||
@ -358,12 +369,13 @@ export async function runAgentTurnWithFallback(params: {
|
|||||||
// Use pipeline if available (block streaming enabled), otherwise send directly
|
// Use pipeline if available (block streaming enabled), otherwise send directly
|
||||||
if (params.blockStreamingEnabled && params.blockReplyPipeline) {
|
if (params.blockStreamingEnabled && params.blockReplyPipeline) {
|
||||||
params.blockReplyPipeline.enqueue(blockPayload);
|
params.blockReplyPipeline.enqueue(blockPayload);
|
||||||
} else {
|
} else if (params.blockStreamingEnabled) {
|
||||||
// Send directly when flushing before tool execution (no streaming).
|
// Send directly when flushing before tool execution (no pipeline but streaming enabled).
|
||||||
// Track sent key to avoid duplicate in final payloads.
|
// Track sent key to avoid duplicate in final payloads.
|
||||||
directlySentBlockKeys.add(createBlockReplyPayloadKey(blockPayload));
|
directlySentBlockKeys.add(createBlockReplyPayloadKey(blockPayload));
|
||||||
await params.opts?.onBlockReply?.(blockPayload);
|
await params.opts?.onBlockReply?.(blockPayload);
|
||||||
}
|
}
|
||||||
|
// When streaming is disabled entirely, blocks are accumulated in final text instead.
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
onBlockReplyFlush:
|
onBlockReplyFlush:
|
||||||
|
|||||||
@ -337,12 +337,56 @@ async function pageTargetId(page: Page): Promise<string | null> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findPageByTargetId(browser: Browser, targetId: string): Promise<Page | null> {
|
async function findPageByTargetId(
|
||||||
|
browser: Browser,
|
||||||
|
targetId: string,
|
||||||
|
cdpUrl?: string,
|
||||||
|
): Promise<Page | null> {
|
||||||
const pages = await getAllPages(browser);
|
const pages = await getAllPages(browser);
|
||||||
|
// First, try the standard CDP session approach
|
||||||
for (const page of pages) {
|
for (const page of pages) {
|
||||||
const tid = await pageTargetId(page).catch(() => null);
|
const tid = await pageTargetId(page).catch(() => null);
|
||||||
if (tid && tid === targetId) return page;
|
if (tid && tid === targetId) return page;
|
||||||
}
|
}
|
||||||
|
// If CDP sessions fail (e.g., extension relay blocks Target.attachToBrowserTarget),
|
||||||
|
// fall back to URL-based matching using the /json/list endpoint
|
||||||
|
if (cdpUrl) {
|
||||||
|
try {
|
||||||
|
const baseUrl = cdpUrl
|
||||||
|
.replace(/\/+$/, "")
|
||||||
|
.replace(/^ws:/, "http:")
|
||||||
|
.replace(/\/cdp$/, "");
|
||||||
|
const response = await fetch(`${baseUrl}/json/list`);
|
||||||
|
if (response.ok) {
|
||||||
|
const targets = (await response.json()) as Array<{
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
title?: string;
|
||||||
|
}>;
|
||||||
|
const target = targets.find((t) => t.id === targetId);
|
||||||
|
if (target) {
|
||||||
|
// Try to find a page with matching URL
|
||||||
|
const urlMatch = pages.filter((p) => p.url() === target.url);
|
||||||
|
if (urlMatch.length === 1) {
|
||||||
|
return urlMatch[0];
|
||||||
|
}
|
||||||
|
// If multiple URL matches, use index-based matching as fallback
|
||||||
|
// This works when Playwright and the relay enumerate tabs in the same order
|
||||||
|
if (urlMatch.length > 1) {
|
||||||
|
const sameUrlTargets = targets.filter((t) => t.url === target.url);
|
||||||
|
if (sameUrlTargets.length === urlMatch.length) {
|
||||||
|
const idx = sameUrlTargets.findIndex((t) => t.id === targetId);
|
||||||
|
if (idx >= 0 && idx < urlMatch.length) {
|
||||||
|
return urlMatch[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore fetch errors and fall through to return null
|
||||||
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,7 +399,7 @@ export async function getPageForTargetId(opts: {
|
|||||||
if (!pages.length) throw new Error("No pages available in the connected browser.");
|
if (!pages.length) throw new Error("No pages available in the connected browser.");
|
||||||
const first = pages[0];
|
const first = pages[0];
|
||||||
if (!opts.targetId) return first;
|
if (!opts.targetId) return first;
|
||||||
const found = await findPageByTargetId(browser, opts.targetId);
|
const found = await findPageByTargetId(browser, opts.targetId, opts.cdpUrl);
|
||||||
if (!found) {
|
if (!found) {
|
||||||
// Extension relays can block CDP attachment APIs (e.g. Target.attachToBrowserTarget),
|
// Extension relays can block CDP attachment APIs (e.g. Target.attachToBrowserTarget),
|
||||||
// which prevents us from resolving a page's targetId via newCDPSession(). If Playwright
|
// which prevents us from resolving a page's targetId via newCDPSession(). If Playwright
|
||||||
@ -496,7 +540,7 @@ export async function closePageByTargetIdViaPlaywright(opts: {
|
|||||||
targetId: string;
|
targetId: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { browser } = await connectBrowser(opts.cdpUrl);
|
const { browser } = await connectBrowser(opts.cdpUrl);
|
||||||
const page = await findPageByTargetId(browser, opts.targetId);
|
const page = await findPageByTargetId(browser, opts.targetId, opts.cdpUrl);
|
||||||
if (!page) {
|
if (!page) {
|
||||||
throw new Error("tab not found");
|
throw new Error("tab not found");
|
||||||
}
|
}
|
||||||
@ -512,7 +556,7 @@ export async function focusPageByTargetIdViaPlaywright(opts: {
|
|||||||
targetId: string;
|
targetId: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { browser } = await connectBrowser(opts.cdpUrl);
|
const { browser } = await connectBrowser(opts.cdpUrl);
|
||||||
const page = await findPageByTargetId(browser, opts.targetId);
|
const page = await findPageByTargetId(browser, opts.targetId, opts.cdpUrl);
|
||||||
if (!page) {
|
if (!page) {
|
||||||
throw new Error("tab not found");
|
throw new Error("tab not found");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,11 +13,9 @@ const providerId = "telegram";
|
|||||||
function readTelegramSendParams(params: Record<string, unknown>) {
|
function readTelegramSendParams(params: Record<string, unknown>) {
|
||||||
const to = readStringParam(params, "to", { required: true });
|
const to = readStringParam(params, "to", { required: true });
|
||||||
const mediaUrl = readStringParam(params, "media", { trim: false });
|
const mediaUrl = readStringParam(params, "media", { trim: false });
|
||||||
const content =
|
const message = readStringParam(params, "message", { required: !mediaUrl, allowEmpty: true });
|
||||||
readStringParam(params, "message", {
|
const caption = readStringParam(params, "caption", { allowEmpty: true });
|
||||||
required: !mediaUrl,
|
const content = message || caption || "";
|
||||||
allowEmpty: true,
|
|
||||||
}) ?? "";
|
|
||||||
const replyTo = readStringParam(params, "replyTo");
|
const replyTo = readStringParam(params, "replyTo");
|
||||||
const threadId = readStringParam(params, "threadId");
|
const threadId = readStringParam(params, "threadId");
|
||||||
const buttons = params.buttons;
|
const buttons = params.buttons;
|
||||||
|
|||||||
@ -10,6 +10,61 @@ export async function noteSecurityWarnings(cfg: ClawdbotConfig) {
|
|||||||
const warnings: string[] = [];
|
const warnings: string[] = [];
|
||||||
const auditHint = `- Run: ${formatCliCommand("clawdbot security audit --deep")}`;
|
const auditHint = `- Run: ${formatCliCommand("clawdbot security audit --deep")}`;
|
||||||
|
|
||||||
|
// ===========================================
|
||||||
|
// GATEWAY NETWORK EXPOSURE CHECK
|
||||||
|
// ===========================================
|
||||||
|
// Check for dangerous gateway binding configurations
|
||||||
|
// that expose the gateway to network without proper auth
|
||||||
|
|
||||||
|
const gatewayBind = cfg.gateway?.bind ?? "loopback";
|
||||||
|
const customBindHost = cfg.gateway?.customBindHost?.trim();
|
||||||
|
const authMode = cfg.gateway?.auth?.mode ?? "off";
|
||||||
|
const authToken = cfg.gateway?.auth?.token;
|
||||||
|
const authPassword = cfg.gateway?.auth?.password;
|
||||||
|
|
||||||
|
const isLoopbackBindHost = (host: string) => {
|
||||||
|
const normalized = host.trim().toLowerCase();
|
||||||
|
return (
|
||||||
|
normalized === "localhost" ||
|
||||||
|
normalized === "::1" ||
|
||||||
|
normalized === "[::1]" ||
|
||||||
|
normalized.startsWith("127.")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bindings that expose gateway beyond localhost
|
||||||
|
const exposedBindings = ["all", "lan", "0.0.0.0"];
|
||||||
|
const isExposed =
|
||||||
|
exposedBindings.includes(gatewayBind) ||
|
||||||
|
(gatewayBind === "custom" && (!customBindHost || !isLoopbackBindHost(customBindHost)));
|
||||||
|
|
||||||
|
if (isExposed) {
|
||||||
|
if (authMode === "off") {
|
||||||
|
warnings.push(
|
||||||
|
`- CRITICAL: Gateway bound to "${gatewayBind}" with NO authentication.`,
|
||||||
|
` Anyone on your network (or internet if port-forwarded) can fully control your agent.`,
|
||||||
|
` Fix: ${formatCliCommand("clawdbot config set gateway.bind loopback")}`,
|
||||||
|
` Or enable auth: ${formatCliCommand("clawdbot config set gateway.auth.mode token")}`,
|
||||||
|
);
|
||||||
|
} else if (authMode === "token" && !authToken) {
|
||||||
|
warnings.push(
|
||||||
|
`- CRITICAL: Gateway bound to "${gatewayBind}" with empty auth token.`,
|
||||||
|
` Fix: ${formatCliCommand("clawdbot doctor --fix")} to generate a token`,
|
||||||
|
);
|
||||||
|
} else if (authMode === "password" && !authPassword) {
|
||||||
|
warnings.push(
|
||||||
|
`- CRITICAL: Gateway bound to "${gatewayBind}" with empty password.`,
|
||||||
|
` Fix: ${formatCliCommand("clawdbot configure")} to set a password`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Auth is configured, but still warn about network exposure
|
||||||
|
warnings.push(
|
||||||
|
`- WARNING: Gateway bound to "${gatewayBind}" (network-accessible).`,
|
||||||
|
` Ensure your auth credentials are strong and not exposed.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const warnDmPolicy = async (params: {
|
const warnDmPolicy = async (params: {
|
||||||
label: string;
|
label: string;
|
||||||
provider: ChannelId;
|
provider: ChannelId;
|
||||||
|
|||||||
@ -211,6 +211,11 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
parseJson: (raw) => deps.json5.parse(raw),
|
parseJson: (raw) => deps.json5.parse(raw),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Apply config.env to process.env BEFORE substitution so ${VAR} can reference config-defined vars
|
||||||
|
if (resolved && typeof resolved === "object" && "env" in resolved) {
|
||||||
|
applyConfigEnv(resolved as ClawdbotConfig, deps.env);
|
||||||
|
}
|
||||||
|
|
||||||
// Substitute ${VAR} env var references
|
// Substitute ${VAR} env var references
|
||||||
const substituted = resolveConfigEnvVars(resolved, deps.env);
|
const substituted = resolveConfigEnvVars(resolved, deps.env);
|
||||||
|
|
||||||
@ -365,6 +370,11 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply config.env to process.env BEFORE substitution so ${VAR} can reference config-defined vars
|
||||||
|
if (resolved && typeof resolved === "object" && "env" in resolved) {
|
||||||
|
applyConfigEnv(resolved as ClawdbotConfig, deps.env);
|
||||||
|
}
|
||||||
|
|
||||||
// Substitute ${VAR} env var references
|
// Substitute ${VAR} env var references
|
||||||
let substituted: unknown;
|
let substituted: unknown;
|
||||||
try {
|
try {
|
||||||
|
|||||||
43
src/gateway/server-chat.agent-events.test.ts
Normal file
43
src/gateway/server-chat.agent-events.test.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { createAgentEventHandler, createChatRunState } from "./server-chat.js";
|
||||||
|
|
||||||
|
describe("agent event handler", () => {
|
||||||
|
it("emits chat delta for assistant text-only events", () => {
|
||||||
|
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(1_000);
|
||||||
|
const broadcast = vi.fn();
|
||||||
|
const nodeSendToSession = vi.fn();
|
||||||
|
const agentRunSeq = new Map<string, number>();
|
||||||
|
const chatRunState = createChatRunState();
|
||||||
|
chatRunState.registry.add("run-1", { sessionKey: "session-1", clientRunId: "client-1" });
|
||||||
|
|
||||||
|
const handler = createAgentEventHandler({
|
||||||
|
broadcast,
|
||||||
|
nodeSendToSession,
|
||||||
|
agentRunSeq,
|
||||||
|
chatRunState,
|
||||||
|
resolveSessionKeyForRun: () => undefined,
|
||||||
|
clearAgentRunContext: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
handler({
|
||||||
|
runId: "run-1",
|
||||||
|
seq: 1,
|
||||||
|
stream: "assistant",
|
||||||
|
ts: Date.now(),
|
||||||
|
data: { text: "Hello world" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const chatCalls = broadcast.mock.calls.filter(([event]) => event === "chat");
|
||||||
|
expect(chatCalls).toHaveLength(1);
|
||||||
|
const payload = chatCalls[0]?.[1] as {
|
||||||
|
state?: string;
|
||||||
|
message?: { content?: Array<{ text?: string }> };
|
||||||
|
};
|
||||||
|
expect(payload.state).toBe("delta");
|
||||||
|
expect(payload.message?.content?.[0]?.text).toBe("Hello world");
|
||||||
|
const sessionChatCalls = nodeSendToSession.mock.calls.filter(([, event]) => event === "chat");
|
||||||
|
expect(sessionChatCalls).toHaveLength(1);
|
||||||
|
nowSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
163
src/gateway/server-methods/agent.test.ts
Normal file
163
src/gateway/server-methods/agent.test.ts
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import type { GatewayRequestContext } from "./types.js";
|
||||||
|
import { agentHandlers } from "./agent.js";
|
||||||
|
|
||||||
|
const mocks = vi.hoisted(() => ({
|
||||||
|
loadSessionEntry: vi.fn(),
|
||||||
|
updateSessionStore: vi.fn(),
|
||||||
|
agentCommand: vi.fn(),
|
||||||
|
registerAgentRunContext: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../session-utils.js", () => ({
|
||||||
|
loadSessionEntry: mocks.loadSessionEntry,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../config/sessions.js", async () => {
|
||||||
|
const actual = await vi.importActual<typeof import("../../config/sessions.js")>(
|
||||||
|
"../../config/sessions.js",
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
updateSessionStore: mocks.updateSessionStore,
|
||||||
|
resolveAgentIdFromSessionKey: () => "main",
|
||||||
|
resolveExplicitAgentSessionKey: () => undefined,
|
||||||
|
resolveAgentMainSessionKey: () => "agent:main:main",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock("../../commands/agent.js", () => ({
|
||||||
|
agentCommand: mocks.agentCommand,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../config/config.js", () => ({
|
||||||
|
loadConfig: () => ({}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../agents/agent-scope.js", () => ({
|
||||||
|
listAgentIds: () => ["main"],
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../infra/agent-events.js", () => ({
|
||||||
|
registerAgentRunContext: mocks.registerAgentRunContext,
|
||||||
|
onAgentEvent: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../sessions/send-policy.js", () => ({
|
||||||
|
resolveSendPolicy: () => "allow",
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../../utils/delivery-context.js", async () => {
|
||||||
|
const actual = await vi.importActual<typeof import("../../utils/delivery-context.js")>(
|
||||||
|
"../../utils/delivery-context.js",
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
normalizeSessionDeliveryFields: () => ({}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const makeContext = (): GatewayRequestContext =>
|
||||||
|
({
|
||||||
|
dedupe: new Map(),
|
||||||
|
addChatRun: vi.fn(),
|
||||||
|
logGateway: { info: vi.fn(), error: vi.fn() },
|
||||||
|
}) as unknown as GatewayRequestContext;
|
||||||
|
|
||||||
|
describe("gateway agent handler", () => {
|
||||||
|
it("preserves cliSessionIds from existing session entry", async () => {
|
||||||
|
const existingCliSessionIds = { "claude-cli": "abc-123-def" };
|
||||||
|
const existingClaudeCliSessionId = "abc-123-def";
|
||||||
|
|
||||||
|
mocks.loadSessionEntry.mockReturnValue({
|
||||||
|
cfg: {},
|
||||||
|
storePath: "/tmp/sessions.json",
|
||||||
|
entry: {
|
||||||
|
sessionId: "existing-session-id",
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
cliSessionIds: existingCliSessionIds,
|
||||||
|
claudeCliSessionId: existingClaudeCliSessionId,
|
||||||
|
},
|
||||||
|
canonicalKey: "agent:main:main",
|
||||||
|
});
|
||||||
|
|
||||||
|
let capturedEntry: Record<string, unknown> | undefined;
|
||||||
|
mocks.updateSessionStore.mockImplementation(async (_path, updater) => {
|
||||||
|
const store: Record<string, unknown> = {};
|
||||||
|
await updater(store);
|
||||||
|
capturedEntry = store["agent:main:main"] as Record<string, unknown>;
|
||||||
|
});
|
||||||
|
|
||||||
|
mocks.agentCommand.mockResolvedValue({
|
||||||
|
payloads: [{ text: "ok" }],
|
||||||
|
meta: { durationMs: 100 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const respond = vi.fn();
|
||||||
|
await agentHandlers.agent({
|
||||||
|
params: {
|
||||||
|
message: "test",
|
||||||
|
agentId: "main",
|
||||||
|
sessionKey: "agent:main:main",
|
||||||
|
idempotencyKey: "test-idem",
|
||||||
|
},
|
||||||
|
respond,
|
||||||
|
context: makeContext(),
|
||||||
|
req: { type: "req", id: "1", method: "agent" },
|
||||||
|
client: null,
|
||||||
|
isWebchatConnect: () => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mocks.updateSessionStore).toHaveBeenCalled();
|
||||||
|
expect(capturedEntry).toBeDefined();
|
||||||
|
expect(capturedEntry?.cliSessionIds).toEqual(existingCliSessionIds);
|
||||||
|
expect(capturedEntry?.claudeCliSessionId).toBe(existingClaudeCliSessionId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles missing cliSessionIds gracefully", async () => {
|
||||||
|
mocks.loadSessionEntry.mockReturnValue({
|
||||||
|
cfg: {},
|
||||||
|
storePath: "/tmp/sessions.json",
|
||||||
|
entry: {
|
||||||
|
sessionId: "existing-session-id",
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
// No cliSessionIds or claudeCliSessionId
|
||||||
|
},
|
||||||
|
canonicalKey: "agent:main:main",
|
||||||
|
});
|
||||||
|
|
||||||
|
let capturedEntry: Record<string, unknown> | undefined;
|
||||||
|
mocks.updateSessionStore.mockImplementation(async (_path, updater) => {
|
||||||
|
const store: Record<string, unknown> = {};
|
||||||
|
await updater(store);
|
||||||
|
capturedEntry = store["agent:main:main"] as Record<string, unknown>;
|
||||||
|
});
|
||||||
|
|
||||||
|
mocks.agentCommand.mockResolvedValue({
|
||||||
|
payloads: [{ text: "ok" }],
|
||||||
|
meta: { durationMs: 100 },
|
||||||
|
});
|
||||||
|
|
||||||
|
const respond = vi.fn();
|
||||||
|
await agentHandlers.agent({
|
||||||
|
params: {
|
||||||
|
message: "test",
|
||||||
|
agentId: "main",
|
||||||
|
sessionKey: "agent:main:main",
|
||||||
|
idempotencyKey: "test-idem-2",
|
||||||
|
},
|
||||||
|
respond,
|
||||||
|
context: makeContext(),
|
||||||
|
req: { type: "req", id: "2", method: "agent" },
|
||||||
|
client: null,
|
||||||
|
isWebchatConnect: () => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mocks.updateSessionStore).toHaveBeenCalled();
|
||||||
|
expect(capturedEntry).toBeDefined();
|
||||||
|
// Should be undefined, not cause an error
|
||||||
|
expect(capturedEntry?.cliSessionIds).toBeUndefined();
|
||||||
|
expect(capturedEntry?.claudeCliSessionId).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -251,6 +251,8 @@ export const agentHandlers: GatewayRequestHandlers = {
|
|||||||
groupId: resolvedGroupId ?? entry?.groupId,
|
groupId: resolvedGroupId ?? entry?.groupId,
|
||||||
groupChannel: resolvedGroupChannel ?? entry?.groupChannel,
|
groupChannel: resolvedGroupChannel ?? entry?.groupChannel,
|
||||||
space: resolvedGroupSpace ?? entry?.space,
|
space: resolvedGroupSpace ?? entry?.space,
|
||||||
|
cliSessionIds: entry?.cliSessionIds,
|
||||||
|
claudeCliSessionId: entry?.claudeCliSessionId,
|
||||||
};
|
};
|
||||||
sessionEntry = nextEntry;
|
sessionEntry = nextEntry;
|
||||||
const sendPolicy = resolveSendPolicy({
|
const sendPolicy = resolveSendPolicy({
|
||||||
|
|||||||
@ -28,11 +28,16 @@ export async function scheduleRestartSentinelWake(params: { deps: CliDeps }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const threadMarker = ":thread:";
|
// Extract topic/thread ID from sessionKey (supports both :topic: and :thread:)
|
||||||
const threadIndex = sessionKey.lastIndexOf(threadMarker);
|
// Telegram uses :topic:, other platforms use :thread:
|
||||||
const baseSessionKey = threadIndex === -1 ? sessionKey : sessionKey.slice(0, threadIndex);
|
const topicIndex = sessionKey.lastIndexOf(":topic:");
|
||||||
|
const threadIndex = sessionKey.lastIndexOf(":thread:");
|
||||||
|
const markerIndex = Math.max(topicIndex, threadIndex);
|
||||||
|
const marker = topicIndex > threadIndex ? ":topic:" : ":thread:";
|
||||||
|
|
||||||
|
const baseSessionKey = markerIndex === -1 ? sessionKey : sessionKey.slice(0, markerIndex);
|
||||||
const threadIdRaw =
|
const threadIdRaw =
|
||||||
threadIndex === -1 ? undefined : sessionKey.slice(threadIndex + threadMarker.length);
|
markerIndex === -1 ? undefined : sessionKey.slice(markerIndex + marker.length);
|
||||||
const sessionThreadId = threadIdRaw?.trim() || undefined;
|
const sessionThreadId = threadIdRaw?.trim() || undefined;
|
||||||
|
|
||||||
const { cfg, entry } = loadSessionEntry(sessionKey);
|
const { cfg, entry } = loadSessionEntry(sessionKey);
|
||||||
@ -42,7 +47,7 @@ export async function scheduleRestartSentinelWake(params: { deps: CliDeps }) {
|
|||||||
// Handles race condition where store wasn't flushed before restart
|
// Handles race condition where store wasn't flushed before restart
|
||||||
const sentinelContext = payload.deliveryContext;
|
const sentinelContext = payload.deliveryContext;
|
||||||
let sessionDeliveryContext = deliveryContextFromSession(entry);
|
let sessionDeliveryContext = deliveryContextFromSession(entry);
|
||||||
if (!sessionDeliveryContext && threadIndex !== -1 && baseSessionKey) {
|
if (!sessionDeliveryContext && markerIndex !== -1 && baseSessionKey) {
|
||||||
const { entry: baseEntry } = loadSessionEntry(baseSessionKey);
|
const { entry: baseEntry } = loadSessionEntry(baseSessionKey);
|
||||||
sessionDeliveryContext = deliveryContextFromSession(baseEntry);
|
sessionDeliveryContext = deliveryContextFromSession(baseEntry);
|
||||||
}
|
}
|
||||||
@ -74,6 +79,7 @@ export async function scheduleRestartSentinelWake(params: { deps: CliDeps }) {
|
|||||||
|
|
||||||
const threadId =
|
const threadId =
|
||||||
payload.threadId ??
|
payload.threadId ??
|
||||||
|
parsedTarget?.threadId ?? // From resolveAnnounceTargetFromKey (extracts :topic:N)
|
||||||
sessionThreadId ??
|
sessionThreadId ??
|
||||||
(origin?.threadId != null ? String(origin.threadId) : undefined);
|
(origin?.threadId != null ? String(origin.threadId) : undefined);
|
||||||
|
|
||||||
|
|||||||
@ -129,9 +129,10 @@ export async function checkGitUpdateStatus(params: {
|
|||||||
).catch(() => null);
|
).catch(() => null);
|
||||||
const upstream = upstreamRes && upstreamRes.code === 0 ? upstreamRes.stdout.trim() : null;
|
const upstream = upstreamRes && upstreamRes.code === 0 ? upstreamRes.stdout.trim() : null;
|
||||||
|
|
||||||
const dirtyRes = await runCommandWithTimeout(["git", "-C", root, "status", "--porcelain"], {
|
const dirtyRes = await runCommandWithTimeout(
|
||||||
timeoutMs,
|
["git", "-C", root, "status", "--porcelain", "--", ":!dist/control-ui/"],
|
||||||
}).catch(() => null);
|
{ timeoutMs },
|
||||||
|
).catch(() => null);
|
||||||
const dirty = dirtyRes && dirtyRes.code === 0 ? dirtyRes.stdout.trim().length > 0 : null;
|
const dirty = dirtyRes && dirtyRes.code === 0 ? dirtyRes.stdout.trim().length > 0 : null;
|
||||||
|
|
||||||
const fetchOk = params.fetch
|
const fetchOk = params.fetch
|
||||||
|
|||||||
@ -44,7 +44,7 @@ describe("runGatewayUpdate", () => {
|
|||||||
[`git -C ${tempDir} rev-parse --show-toplevel`]: { stdout: tempDir },
|
[`git -C ${tempDir} rev-parse --show-toplevel`]: { stdout: tempDir },
|
||||||
[`git -C ${tempDir} rev-parse HEAD`]: { stdout: "abc123" },
|
[`git -C ${tempDir} rev-parse HEAD`]: { stdout: "abc123" },
|
||||||
[`git -C ${tempDir} rev-parse --abbrev-ref HEAD`]: { stdout: "main" },
|
[`git -C ${tempDir} rev-parse --abbrev-ref HEAD`]: { stdout: "main" },
|
||||||
[`git -C ${tempDir} status --porcelain`]: { stdout: " M README.md" },
|
[`git -C ${tempDir} status --porcelain -- :!dist/control-ui/`]: { stdout: " M README.md" },
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await runGatewayUpdate({
|
const result = await runGatewayUpdate({
|
||||||
@ -69,7 +69,7 @@ describe("runGatewayUpdate", () => {
|
|||||||
[`git -C ${tempDir} rev-parse --show-toplevel`]: { stdout: tempDir },
|
[`git -C ${tempDir} rev-parse --show-toplevel`]: { stdout: tempDir },
|
||||||
[`git -C ${tempDir} rev-parse HEAD`]: { stdout: "abc123" },
|
[`git -C ${tempDir} rev-parse HEAD`]: { stdout: "abc123" },
|
||||||
[`git -C ${tempDir} rev-parse --abbrev-ref HEAD`]: { stdout: "main" },
|
[`git -C ${tempDir} rev-parse --abbrev-ref HEAD`]: { stdout: "main" },
|
||||||
[`git -C ${tempDir} status --porcelain`]: { stdout: "" },
|
[`git -C ${tempDir} status --porcelain -- :!dist/control-ui/`]: { stdout: "" },
|
||||||
[`git -C ${tempDir} rev-parse --abbrev-ref --symbolic-full-name @{upstream}`]: {
|
[`git -C ${tempDir} rev-parse --abbrev-ref --symbolic-full-name @{upstream}`]: {
|
||||||
stdout: "origin/main",
|
stdout: "origin/main",
|
||||||
},
|
},
|
||||||
@ -103,7 +103,7 @@ describe("runGatewayUpdate", () => {
|
|||||||
const { runner, calls } = createRunner({
|
const { runner, calls } = createRunner({
|
||||||
[`git -C ${tempDir} rev-parse --show-toplevel`]: { stdout: tempDir },
|
[`git -C ${tempDir} rev-parse --show-toplevel`]: { stdout: tempDir },
|
||||||
[`git -C ${tempDir} rev-parse HEAD`]: { stdout: "abc123" },
|
[`git -C ${tempDir} rev-parse HEAD`]: { stdout: "abc123" },
|
||||||
[`git -C ${tempDir} status --porcelain`]: { stdout: "" },
|
[`git -C ${tempDir} status --porcelain -- :!dist/control-ui/`]: { stdout: "" },
|
||||||
[`git -C ${tempDir} fetch --all --prune --tags`]: { stdout: "" },
|
[`git -C ${tempDir} fetch --all --prune --tags`]: { stdout: "" },
|
||||||
[`git -C ${tempDir} tag --list v* --sort=-v:refname`]: {
|
[`git -C ${tempDir} tag --list v* --sort=-v:refname`]: {
|
||||||
stdout: `${stableTag}\n${betaTag}\n`,
|
stdout: `${stableTag}\n${betaTag}\n`,
|
||||||
@ -112,6 +112,7 @@ describe("runGatewayUpdate", () => {
|
|||||||
"pnpm install": { stdout: "" },
|
"pnpm install": { stdout: "" },
|
||||||
"pnpm build": { stdout: "" },
|
"pnpm build": { stdout: "" },
|
||||||
"pnpm ui:build": { stdout: "" },
|
"pnpm ui:build": { stdout: "" },
|
||||||
|
[`git -C ${tempDir} checkout -- dist/control-ui/`]: { stdout: "" },
|
||||||
"pnpm clawdbot doctor --non-interactive": { stdout: "" },
|
"pnpm clawdbot doctor --non-interactive": { stdout: "" },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -346,10 +346,14 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise<
|
|||||||
const channel: UpdateChannel = opts.channel ?? "dev";
|
const channel: UpdateChannel = opts.channel ?? "dev";
|
||||||
const branch = channel === "dev" ? await readBranchName(runCommand, gitRoot, timeoutMs) : null;
|
const branch = channel === "dev" ? await readBranchName(runCommand, gitRoot, timeoutMs) : null;
|
||||||
const needsCheckoutMain = channel === "dev" && branch !== DEV_BRANCH;
|
const needsCheckoutMain = channel === "dev" && branch !== DEV_BRANCH;
|
||||||
gitTotalSteps = channel === "dev" ? (needsCheckoutMain ? 10 : 9) : 8;
|
gitTotalSteps = channel === "dev" ? (needsCheckoutMain ? 11 : 10) : 9;
|
||||||
|
|
||||||
const statusCheck = await runStep(
|
const statusCheck = await runStep(
|
||||||
step("clean check", ["git", "-C", gitRoot, "status", "--porcelain"], gitRoot),
|
step(
|
||||||
|
"clean check",
|
||||||
|
["git", "-C", gitRoot, "status", "--porcelain", "--", ":!dist/control-ui/"],
|
||||||
|
gitRoot,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
steps.push(statusCheck);
|
steps.push(statusCheck);
|
||||||
const hasUncommittedChanges =
|
const hasUncommittedChanges =
|
||||||
@ -654,6 +658,17 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise<
|
|||||||
);
|
);
|
||||||
steps.push(uiBuildStep);
|
steps.push(uiBuildStep);
|
||||||
|
|
||||||
|
// Restore dist/control-ui/ to committed state to prevent dirty repo after update
|
||||||
|
// (ui:build regenerates assets with new hashes, which would block future updates)
|
||||||
|
const restoreUiStep = await runStep(
|
||||||
|
step(
|
||||||
|
"restore control-ui",
|
||||||
|
["git", "-C", gitRoot, "checkout", "--", "dist/control-ui/"],
|
||||||
|
gitRoot,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
steps.push(restoreUiStep);
|
||||||
|
|
||||||
const doctorStep = await runStep(
|
const doctorStep = await runStep(
|
||||||
step(
|
step(
|
||||||
"clawdbot doctor",
|
"clawdbot doctor",
|
||||||
|
|||||||
@ -11,6 +11,12 @@ export const DEFAULT_AGENT_ID = "main";
|
|||||||
export const DEFAULT_MAIN_KEY = "main";
|
export const DEFAULT_MAIN_KEY = "main";
|
||||||
export const DEFAULT_ACCOUNT_ID = "default";
|
export const DEFAULT_ACCOUNT_ID = "default";
|
||||||
|
|
||||||
|
// Pre-compiled regex
|
||||||
|
const VALID_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
|
||||||
|
const INVALID_CHARS_RE = /[^a-z0-9_-]+/g;
|
||||||
|
const LEADING_DASH_RE = /^-+/;
|
||||||
|
const TRAILING_DASH_RE = /-+$/;
|
||||||
|
|
||||||
function normalizeToken(value: string | undefined | null): string {
|
function normalizeToken(value: string | undefined | null): string {
|
||||||
return (value ?? "").trim().toLowerCase();
|
return (value ?? "").trim().toLowerCase();
|
||||||
}
|
}
|
||||||
@ -52,14 +58,14 @@ export function normalizeAgentId(value: string | undefined | null): string {
|
|||||||
const trimmed = (value ?? "").trim();
|
const trimmed = (value ?? "").trim();
|
||||||
if (!trimmed) return DEFAULT_AGENT_ID;
|
if (!trimmed) return DEFAULT_AGENT_ID;
|
||||||
// Keep it path-safe + shell-friendly.
|
// Keep it path-safe + shell-friendly.
|
||||||
if (/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(trimmed)) return trimmed.toLowerCase();
|
if (VALID_ID_RE.test(trimmed)) return trimmed.toLowerCase();
|
||||||
// Best-effort fallback: collapse invalid characters to "-"
|
// Best-effort fallback: collapse invalid characters to "-"
|
||||||
return (
|
return (
|
||||||
trimmed
|
trimmed
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/[^a-z0-9_-]+/g, "-")
|
.replace(INVALID_CHARS_RE, "-")
|
||||||
.replace(/^-+/, "")
|
.replace(LEADING_DASH_RE, "")
|
||||||
.replace(/-+$/, "")
|
.replace(TRAILING_DASH_RE, "")
|
||||||
.slice(0, 64) || DEFAULT_AGENT_ID
|
.slice(0, 64) || DEFAULT_AGENT_ID
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -67,13 +73,13 @@ export function normalizeAgentId(value: string | undefined | null): string {
|
|||||||
export function sanitizeAgentId(value: string | undefined | null): string {
|
export function sanitizeAgentId(value: string | undefined | null): string {
|
||||||
const trimmed = (value ?? "").trim();
|
const trimmed = (value ?? "").trim();
|
||||||
if (!trimmed) return DEFAULT_AGENT_ID;
|
if (!trimmed) return DEFAULT_AGENT_ID;
|
||||||
if (/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(trimmed)) return trimmed.toLowerCase();
|
if (VALID_ID_RE.test(trimmed)) return trimmed.toLowerCase();
|
||||||
return (
|
return (
|
||||||
trimmed
|
trimmed
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/[^a-z0-9_-]+/gi, "-")
|
.replace(INVALID_CHARS_RE, "-")
|
||||||
.replace(/^-+/, "")
|
.replace(LEADING_DASH_RE, "")
|
||||||
.replace(/-+$/, "")
|
.replace(TRAILING_DASH_RE, "")
|
||||||
.slice(0, 64) || DEFAULT_AGENT_ID
|
.slice(0, 64) || DEFAULT_AGENT_ID
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -81,13 +87,13 @@ export function sanitizeAgentId(value: string | undefined | null): string {
|
|||||||
export function normalizeAccountId(value: string | undefined | null): string {
|
export function normalizeAccountId(value: string | undefined | null): string {
|
||||||
const trimmed = (value ?? "").trim();
|
const trimmed = (value ?? "").trim();
|
||||||
if (!trimmed) return DEFAULT_ACCOUNT_ID;
|
if (!trimmed) return DEFAULT_ACCOUNT_ID;
|
||||||
if (/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(trimmed)) return trimmed.toLowerCase();
|
if (VALID_ID_RE.test(trimmed)) return trimmed.toLowerCase();
|
||||||
return (
|
return (
|
||||||
trimmed
|
trimmed
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/[^a-z0-9_-]+/g, "-")
|
.replace(INVALID_CHARS_RE, "-")
|
||||||
.replace(/^-+/, "")
|
.replace(LEADING_DASH_RE, "")
|
||||||
.replace(/-+$/, "")
|
.replace(TRAILING_DASH_RE, "")
|
||||||
.slice(0, 64) || DEFAULT_ACCOUNT_ID
|
.slice(0, 64) || DEFAULT_ACCOUNT_ID
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -141,7 +141,9 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag
|
|||||||
});
|
});
|
||||||
markDispatchIdle();
|
markDispatchIdle();
|
||||||
|
|
||||||
if (!queuedFinal) {
|
const anyReplyDelivered = queuedFinal || (counts.block ?? 0) > 0 || (counts.final ?? 0) > 0;
|
||||||
|
|
||||||
|
if (!anyReplyDelivered) {
|
||||||
if (prepared.isRoomish) {
|
if (prepared.isRoomish) {
|
||||||
clearHistoryEntriesIfEnabled({
|
clearHistoryEntriesIfEnabled({
|
||||||
historyMap: ctx.channelHistories,
|
historyMap: ctx.channelHistories,
|
||||||
|
|||||||
@ -69,7 +69,7 @@ export class FilterableSelectList implements Component {
|
|||||||
lines.push(filterLabel + inputText);
|
lines.push(filterLabel + inputText);
|
||||||
|
|
||||||
// Separator
|
// Separator
|
||||||
lines.push(chalk.dim("─".repeat(width)));
|
lines.push(chalk.dim("─".repeat(Math.max(0, width))));
|
||||||
|
|
||||||
// Select list
|
// Select list
|
||||||
const listLines = this.selectList.render(width);
|
const listLines = this.selectList.render(width);
|
||||||
|
|||||||
@ -214,7 +214,8 @@ export class SearchableSelectList implements Component {
|
|||||||
const maxValueWidth = Math.min(30, width - prefixWidth - 4);
|
const maxValueWidth = Math.min(30, width - prefixWidth - 4);
|
||||||
const truncatedValue = truncateToWidth(displayValue, maxValueWidth, "");
|
const truncatedValue = truncateToWidth(displayValue, maxValueWidth, "");
|
||||||
const valueText = this.highlightMatch(truncatedValue, query);
|
const valueText = this.highlightMatch(truncatedValue, query);
|
||||||
const spacing = " ".repeat(Math.max(1, 32 - visibleWidth(valueText)));
|
const spacingWidth = Math.max(1, 32 - visibleWidth(valueText));
|
||||||
|
const spacing = " ".repeat(spacingWidth);
|
||||||
const descriptionStart = prefixWidth + visibleWidth(valueText) + spacing.length;
|
const descriptionStart = prefixWidth + visibleWidth(valueText) + spacing.length;
|
||||||
const remainingWidth = width - descriptionStart - 2;
|
const remainingWidth = width - descriptionStart - 2;
|
||||||
if (remainingWidth > 10) {
|
if (remainingWidth > 10) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user