Merge branch 'main' into patch-4
This commit is contained in:
commit
b9d39cf414
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -76,7 +76,7 @@ jobs:
|
||||
command: pnpm lint
|
||||
- runtime: node
|
||||
task: test
|
||||
command: pnpm test
|
||||
command: pnpm canvas:a2ui:bundle && pnpm test
|
||||
- runtime: node
|
||||
task: build
|
||||
command: pnpm build
|
||||
@ -88,7 +88,7 @@ jobs:
|
||||
command: pnpm format
|
||||
- runtime: bun
|
||||
task: test
|
||||
command: bunx vitest run
|
||||
command: pnpm canvas:a2ui:bundle && bunx vitest run
|
||||
- runtime: bun
|
||||
task: build
|
||||
command: bunx tsc -p tsconfig.json
|
||||
@ -188,6 +188,7 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-windows-2025
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
CLAWDBOT_TEST_WORKERS: 1
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
@ -200,7 +201,7 @@ jobs:
|
||||
command: pnpm lint
|
||||
- runtime: node
|
||||
task: test
|
||||
command: pnpm test
|
||||
command: pnpm canvas:a2ui:bundle && pnpm test
|
||||
- runtime: node
|
||||
task: build
|
||||
command: pnpm build
|
||||
|
||||
2
.github/workflows/install-smoke.yml
vendored
2
.github/workflows/install-smoke.yml
vendored
@ -37,5 +37,5 @@ jobs:
|
||||
CLAWDBOT_NO_ONBOARD: "1"
|
||||
CLAWDBOT_INSTALL_SMOKE_SKIP_CLI: "1"
|
||||
CLAWDBOT_INSTALL_SMOKE_SKIP_NONROOT: ${{ github.event_name == 'pull_request' && '1' || '0' }}
|
||||
CLAWDBOT_INSTALL_SMOKE_PREVIOUS: "2026.1.11-4"
|
||||
CLAWDBOT_INSTALL_SMOKE_SKIP_PREVIOUS: "1"
|
||||
run: pnpm test:install:smoke
|
||||
|
||||
@ -72,6 +72,7 @@ Status: unreleased.
|
||||
- Security: pin npm overrides to keep tar@7.5.4 for install toolchains.
|
||||
- Security: properly test Windows ACL audit for config includes. (#2403) Thanks @dominicnunez.
|
||||
- CLI: recognize versioned Node executables when parsing argv. (#2490) Thanks @David-Marsh-Photo.
|
||||
- CLI: avoid prompting for gateway runtime under the spinner. (#2874)
|
||||
- BlueBubbles: coalesce inbound URL link preview messages. (#1981) Thanks @tyler6204.
|
||||
- Cron: allow payloads containing "heartbeat" in event filter. (#2219) Thanks @dwfinkelstein.
|
||||
- CLI: avoid loading config for global help/version while registering plugin commands. (#2212) Thanks @dial481.
|
||||
|
||||
@ -24,7 +24,7 @@ COPY scripts ./scripts
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
RUN pnpm build
|
||||
RUN CLAWDBOT_A2UI_SKIP_MISSING=1 pnpm build
|
||||
# Force pnpm for UI build (Bun may fail on ARM/Synology architectures)
|
||||
ENV CLAWDBOT_PREFER_PNPM=1
|
||||
RUN pnpm ui:install
|
||||
|
||||
53
README.md
53
README.md
@ -1,7 +1,7 @@
|
||||
# 🦞 Clawdbot — Personal AI Assistant
|
||||
# 🦞 Moltbot — Personal AI Assistant
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/clawdbot/clawdbot/main/docs/whatsapp-clawd.jpg" alt="Clawdbot" width="400">
|
||||
<img src="https://raw.githubusercontent.com/moltbot/moltbot/main/docs/whatsapp-clawd.jpg" alt="Clawdbot" width="400">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@ -9,21 +9,21 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/clawdbot/clawdbot/actions/workflows/ci.yml?branch=main"><img src="https://img.shields.io/github/actions/workflow/status/clawdbot/clawdbot/ci.yml?branch=main&style=for-the-badge" alt="CI status"></a>
|
||||
<a href="https://github.com/clawdbot/clawdbot/releases"><img src="https://img.shields.io/github/v/release/clawdbot/clawdbot?include_prereleases&style=for-the-badge" alt="GitHub release"></a>
|
||||
<a href="https://deepwiki.com/clawdbot/clawdbot"><img src="https://img.shields.io/badge/DeepWiki-clawdbot-111111?style=for-the-badge" alt="DeepWiki"></a>
|
||||
<a href="https://github.com/moltbot/moltbot/actions/workflows/ci.yml?branch=main"><img src="https://img.shields.io/github/actions/workflow/status/moltbot/moltbot/ci.yml?branch=main&style=for-the-badge" alt="CI status"></a>
|
||||
<a href="https://github.com/moltbot/moltbot/releases"><img src="https://img.shields.io/github/v/release/moltbot/moltbot?include_prereleases&style=for-the-badge" alt="GitHub release"></a>
|
||||
<a href="https://deepwiki.com/moltbot/moltbot"><img src="https://img.shields.io/badge/DeepWiki-moltbot-111111?style=for-the-badge" alt="DeepWiki"></a>
|
||||
<a href="https://discord.gg/clawd"><img src="https://img.shields.io/discord/1456350064065904867?label=Discord&logo=discord&logoColor=white&color=5865F2&style=for-the-badge" alt="Discord"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge" alt="MIT License"></a>
|
||||
</p>
|
||||
|
||||
**Clawdbot** is a *personal AI assistant* you run on your own devices.
|
||||
**Moltbot** is a *personal AI assistant* you run on your own devices.
|
||||
It answers you on the channels you already use (WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, Microsoft Teams, WebChat), plus extension channels like BlueBubbles, Matrix, Zalo, and Zalo Personal. It can speak and listen on macOS/iOS/Android, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant.
|
||||
|
||||
If you want a personal, single-user assistant that feels local, fast, and always-on, this is it.
|
||||
|
||||
[Website](https://molt.bot) · [Docs](https://docs.molt.bot) · [Getting Started](https://docs.molt.bot/start/getting-started) · [Updating](https://docs.molt.bot/install/updating) · [Showcase](https://docs.molt.bot/start/showcase) · [FAQ](https://docs.molt.bot/start/faq) · [Wizard](https://docs.molt.bot/start/wizard) · [Nix](https://github.com/clawdbot/nix-clawdbot) · [Docker](https://docs.molt.bot/install/docker) · [Discord](https://discord.gg/clawd)
|
||||
[Website](https://molt.bot) · [Docs](https://docs.molt.bot) · [Getting Started](https://docs.molt.bot/start/getting-started) · [Updating](https://docs.molt.bot/install/updating) · [Showcase](https://docs.molt.bot/start/showcase) · [FAQ](https://docs.molt.bot/start/faq) · [Wizard](https://docs.molt.bot/start/wizard) · [Nix](https://github.com/moltbot/nix-clawdbot) · [Docker](https://docs.molt.bot/install/docker) · [Discord](https://discord.gg/clawd)
|
||||
|
||||
Preferred setup: run the onboarding wizard (`clawdbot onboard`). It walks through gateway, workspace, channels, and skills. The CLI wizard is the recommended path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**.
|
||||
Preferred setup: run the onboarding wizard (`moltbot onboard`). It walks through gateway, workspace, channels, and skills. The CLI wizard is the recommended path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**.
|
||||
Works with npm, pnpm, or bun.
|
||||
New install? Start here: [Getting started](https://docs.molt.bot/start/getting-started)
|
||||
|
||||
@ -78,7 +78,7 @@ Upgrading? [Updating guide](https://docs.molt.bot/install/updating) (and run `mo
|
||||
- **beta**: prerelease tags (`vYYYY.M.D-beta.N`), npm dist-tag `beta` (macOS app may be missing).
|
||||
- **dev**: moving head of `main`, npm dist-tag `dev` (when published).
|
||||
|
||||
Switch channels (git + npm): `clawdbot update --channel stable|beta|dev`.
|
||||
Switch channels (git + npm): `moltbot update --channel stable|beta|dev`.
|
||||
Details: [Development channels](https://docs.molt.bot/install/development-channels).
|
||||
|
||||
## From source (development)
|
||||
@ -86,8 +86,8 @@ Details: [Development channels](https://docs.molt.bot/install/development-channe
|
||||
Prefer `pnpm` for builds from source. Bun is optional for running TypeScript directly.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/clawdbot/clawdbot.git
|
||||
cd clawdbot
|
||||
git clone https://github.com/moltbot/moltbot.git
|
||||
cd moltbot
|
||||
|
||||
pnpm install
|
||||
pnpm ui:build # auto-installs UI deps on first run
|
||||
@ -103,16 +103,16 @@ Note: `pnpm moltbot ...` runs TypeScript directly (via `tsx`). `pnpm build` prod
|
||||
|
||||
## Security defaults (DM access)
|
||||
|
||||
Clawdbot connects to real messaging surfaces. Treat inbound DMs as **untrusted input**.
|
||||
Moltbot connects to real messaging surfaces. Treat inbound DMs as **untrusted input**.
|
||||
|
||||
Full security guide: [Security](https://docs.molt.bot/gateway/security)
|
||||
|
||||
Default behavior on Telegram/WhatsApp/Signal/iMessage/Microsoft Teams/Discord/Google Chat/Slack:
|
||||
- **DM pairing** (`dmPolicy="pairing"` / `channels.discord.dm.policy="pairing"` / `channels.slack.dm.policy="pairing"`): unknown senders receive a short pairing code and the bot does not process their message.
|
||||
- Approve with: `clawdbot pairing approve <channel> <code>` (then the sender is added to a local allowlist store).
|
||||
- Approve with: `moltbot pairing approve <channel> <code>` (then the sender is added to a local allowlist store).
|
||||
- Public inbound DMs require an explicit opt-in: set `dmPolicy="open"` and include `"*"` in the channel allowlist (`allowFrom` / `channels.discord.dm.allowFrom` / `channels.slack.dm.allowFrom`).
|
||||
|
||||
Run `clawdbot doctor` to surface risky/misconfigured DM policies.
|
||||
Run `moltbot doctor` to surface risky/misconfigured DM policies.
|
||||
|
||||
## Highlights
|
||||
|
||||
@ -127,7 +127,7 @@ Run `clawdbot doctor` to surface risky/misconfigured DM policies.
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://www.star-history.com/#clawdbot/clawdbot&type=date&legend=top-left)
|
||||
[](https://www.star-history.com/#moltbot/moltbot&type=date&legend=top-left)
|
||||
|
||||
## Everything we built so far
|
||||
|
||||
@ -149,7 +149,7 @@ Run `clawdbot doctor` to surface risky/misconfigured DM policies.
|
||||
- [macOS node mode](https://docs.molt.bot/nodes): system.run/notify + canvas/camera exposure.
|
||||
|
||||
### Tools + automation
|
||||
- [Browser control](https://docs.molt.bot/tools/browser): dedicated clawd Chrome/Chromium, snapshots, actions, uploads, profiles.
|
||||
- [Browser control](https://docs.molt.bot/tools/browser): dedicated moltbot Chrome/Chromium, snapshots, actions, uploads, profiles.
|
||||
- [Canvas](https://docs.molt.bot/platforms/mac/canvas): [A2UI](https://docs.molt.bot/platforms/mac/canvas#canvas-a2ui) push/reset, eval, snapshot.
|
||||
- [Nodes](https://docs.molt.bot/nodes): camera snap/clip, screen record, [location.get](https://docs.molt.bot/nodes/location-command), notifications.
|
||||
- [Cron + wakeups](https://docs.molt.bot/automation/cron-jobs); [webhooks](https://docs.molt.bot/automation/webhook); [Gmail Pub/Sub](https://docs.molt.bot/automation/gmail-pubsub).
|
||||
@ -180,7 +180,7 @@ WhatsApp / Telegram / Slack / Discord / Google Chat / Signal / iMessage / BlueBu
|
||||
└──────────────┬────────────────┘
|
||||
│
|
||||
├─ Pi agent (RPC)
|
||||
├─ CLI (clawdbot …)
|
||||
├─ CLI (moltbot …)
|
||||
├─ WebChat UI
|
||||
├─ macOS app
|
||||
└─ iOS / Android nodes
|
||||
@ -190,21 +190,21 @@ WhatsApp / Telegram / Slack / Discord / Google Chat / Signal / iMessage / BlueBu
|
||||
|
||||
- **[Gateway WebSocket network](https://docs.molt.bot/concepts/architecture)** — single WS control plane for clients, tools, and events (plus ops: [Gateway runbook](https://docs.molt.bot/gateway)).
|
||||
- **[Tailscale exposure](https://docs.molt.bot/gateway/tailscale)** — Serve/Funnel for the Gateway dashboard + WS (remote access: [Remote](https://docs.molt.bot/gateway/remote)).
|
||||
- **[Browser control](https://docs.molt.bot/tools/browser)** — clawd‑managed Chrome/Chromium with CDP control.
|
||||
- **[Browser control](https://docs.molt.bot/tools/browser)** — moltbot‑managed Chrome/Chromium with CDP control.
|
||||
- **[Canvas + A2UI](https://docs.molt.bot/platforms/mac/canvas)** — agent‑driven visual workspace (A2UI host: [Canvas/A2UI](https://docs.molt.bot/platforms/mac/canvas#canvas-a2ui)).
|
||||
- **[Voice Wake](https://docs.molt.bot/nodes/voicewake) + [Talk Mode](https://docs.molt.bot/nodes/talk)** — always‑on speech and continuous conversation.
|
||||
- **[Nodes](https://docs.molt.bot/nodes)** — Canvas, camera snap/clip, screen record, `location.get`, notifications, plus macOS‑only `system.run`/`system.notify`.
|
||||
|
||||
## Tailscale access (Gateway dashboard)
|
||||
|
||||
Clawdbot can auto-configure Tailscale **Serve** (tailnet-only) or **Funnel** (public) while the Gateway stays bound to loopback. Configure `gateway.tailscale.mode`:
|
||||
Moltbot can auto-configure Tailscale **Serve** (tailnet-only) or **Funnel** (public) while the Gateway stays bound to loopback. Configure `gateway.tailscale.mode`:
|
||||
|
||||
- `off`: no Tailscale automation (default).
|
||||
- `serve`: tailnet-only HTTPS via `tailscale serve` (uses Tailscale identity headers by default).
|
||||
- `funnel`: public HTTPS via `tailscale funnel` (requires shared password auth).
|
||||
|
||||
Notes:
|
||||
- `gateway.bind` must stay `loopback` when Serve/Funnel is enabled (Clawdbot enforces this).
|
||||
- `gateway.bind` must stay `loopback` when Serve/Funnel is enabled (Moltbot enforces this).
|
||||
- Serve can be forced to require a password by setting `gateway.auth.mode: "password"` or `gateway.auth.allowTailscale: false`.
|
||||
- Funnel refuses to start unless `gateway.auth.mode: "password"` is set.
|
||||
- Optional: `gateway.tailscale.resetOnExit` to undo Serve/Funnel on shutdown.
|
||||
@ -270,7 +270,7 @@ The Gateway alone delivers a great experience. All apps are optional and add ext
|
||||
|
||||
If you plan to build/run companion apps, follow the platform runbooks below.
|
||||
|
||||
### macOS (Clawdbot.app) (optional)
|
||||
### macOS (Moltbot.app) (optional)
|
||||
|
||||
- Menu bar control for the Gateway and health.
|
||||
- Voice Wake + push-to-talk overlay.
|
||||
@ -283,7 +283,7 @@ Note: signed builds required for macOS permissions to stick across rebuilds (see
|
||||
|
||||
- Pairs as a node via the Bridge.
|
||||
- Voice trigger forwarding + Canvas surface.
|
||||
- Controlled via `clawdbot nodes …`.
|
||||
- Controlled via `moltbot nodes …`.
|
||||
|
||||
Runbook: [iOS connect](https://docs.molt.bot/platforms/ios).
|
||||
|
||||
@ -301,7 +301,7 @@ Runbook: [iOS connect](https://docs.molt.bot/platforms/ios).
|
||||
|
||||
## Configuration
|
||||
|
||||
Minimal `~/.clawdbot/clawdbot.json` (model + defaults):
|
||||
Minimal `~/.clawdbot/moltbot.json` (model + defaults):
|
||||
|
||||
```json5
|
||||
{
|
||||
@ -323,7 +323,7 @@ Details: [Security guide](https://docs.molt.bot/gateway/security) · [Docker + s
|
||||
|
||||
### [WhatsApp](https://docs.molt.bot/channels/whatsapp)
|
||||
|
||||
- Link the device: `pnpm clawdbot channels login` (stores creds in `~/.clawdbot/credentials`).
|
||||
- Link the device: `pnpm moltbot channels login` (stores creds in `~/.clawdbot/credentials`).
|
||||
- Allowlist who can talk to the assistant via `channels.whatsapp.allowFrom`.
|
||||
- If `channels.whatsapp.groups` is set, it becomes a group allowlist; include `"*"` to allow all.
|
||||
|
||||
@ -457,14 +457,15 @@ Use these when you’re past the onboarding flow and want the deeper reference.
|
||||
|
||||
- [docs.molt.bot/gmail-pubsub](https://docs.molt.bot/automation/gmail-pubsub)
|
||||
|
||||
## Clawd
|
||||
## Molty
|
||||
|
||||
Clawdbot was built for **Clawd**, a space lobster AI assistant. 🦞
|
||||
Moltbot was built for **Molty**, a space lobster AI assistant. 🦞
|
||||
by Peter Steinberger and the community.
|
||||
|
||||
- [clawd.me](https://clawd.me)
|
||||
- [soul.md](https://soul.md)
|
||||
- [steipete.me](https://steipete.me)
|
||||
- [@moltbot](https://x.com/moltbot)
|
||||
|
||||
## Community
|
||||
|
||||
|
||||
@ -11,7 +11,8 @@
|
||||
"./cli-entry": "./dist/entry.js"
|
||||
},
|
||||
"bin": {
|
||||
"moltbot": "dist/entry.js"
|
||||
"moltbot": "dist/entry.js",
|
||||
"clawdbot": "dist/entry.js"
|
||||
},
|
||||
"files": [
|
||||
"dist/acp/**",
|
||||
@ -22,6 +23,7 @@
|
||||
"dist/cli/**",
|
||||
"dist/commands/**",
|
||||
"dist/config/**",
|
||||
"dist/compat/**",
|
||||
"dist/control-ui/**",
|
||||
"dist/cron/**",
|
||||
"dist/channels/**",
|
||||
|
||||
38
pnpm-lock.yaml
generated
38
pnpm-lock.yaml
generated
@ -314,17 +314,17 @@ importers:
|
||||
specifier: ^10.5.0
|
||||
version: 10.5.0
|
||||
devDependencies:
|
||||
clawdbot:
|
||||
moltbot:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/clawdbot
|
||||
version: link:../..
|
||||
|
||||
extensions/imessage: {}
|
||||
|
||||
extensions/line:
|
||||
devDependencies:
|
||||
clawdbot:
|
||||
moltbot:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/clawdbot
|
||||
version: link:../..
|
||||
|
||||
extensions/llm-task: {}
|
||||
|
||||
@ -348,17 +348,17 @@ importers:
|
||||
specifier: ^4.3.6
|
||||
version: 4.3.6
|
||||
devDependencies:
|
||||
clawdbot:
|
||||
moltbot:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/clawdbot
|
||||
version: link:../..
|
||||
|
||||
extensions/mattermost: {}
|
||||
|
||||
extensions/memory-core:
|
||||
dependencies:
|
||||
clawdbot:
|
||||
specifier: '>=2026.1.24-3'
|
||||
version: 2026.1.24-3(@types/express@5.0.6)(audio-decode@2.2.3)(devtools-protocol@0.0.1561482)(typescript@5.9.3)
|
||||
moltbot:
|
||||
specifier: '>=2026.1.26'
|
||||
version: link:../..
|
||||
|
||||
extensions/memory-lancedb:
|
||||
dependencies:
|
||||
@ -383,9 +383,9 @@ importers:
|
||||
'@microsoft/agents-hosting-extensions-teams':
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2
|
||||
clawdbot:
|
||||
moltbot:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/clawdbot
|
||||
version: link:../..
|
||||
express:
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.1
|
||||
@ -397,9 +397,9 @@ importers:
|
||||
|
||||
extensions/nostr:
|
||||
dependencies:
|
||||
clawdbot:
|
||||
moltbot:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/clawdbot
|
||||
version: link:../..
|
||||
nostr-tools:
|
||||
specifier: ^2.20.0
|
||||
version: 2.20.0(typescript@5.9.3)
|
||||
@ -439,9 +439,9 @@ importers:
|
||||
specifier: ^4.3.5
|
||||
version: 4.3.6
|
||||
devDependencies:
|
||||
clawdbot:
|
||||
moltbot:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/clawdbot
|
||||
version: link:../..
|
||||
|
||||
extensions/voice-call:
|
||||
dependencies:
|
||||
@ -459,9 +459,9 @@ importers:
|
||||
|
||||
extensions/zalo:
|
||||
dependencies:
|
||||
clawdbot:
|
||||
moltbot:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/clawdbot
|
||||
version: link:../..
|
||||
undici:
|
||||
specifier: 7.19.0
|
||||
version: 7.19.0
|
||||
@ -471,9 +471,9 @@ importers:
|
||||
'@sinclair/typebox':
|
||||
specifier: 0.34.47
|
||||
version: 0.34.47
|
||||
clawdbot:
|
||||
moltbot:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/clawdbot
|
||||
version: link:../..
|
||||
|
||||
packages/clawdbot:
|
||||
dependencies:
|
||||
|
||||
@ -10,31 +10,66 @@ trap on_error ERR
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
HASH_FILE="$ROOT_DIR/src/canvas-host/a2ui/.bundle.hash"
|
||||
OUTPUT_FILE="$ROOT_DIR/src/canvas-host/a2ui/a2ui.bundle.js"
|
||||
A2UI_RENDERER_DIR="$ROOT_DIR/vendor/a2ui/renderers/lit"
|
||||
A2UI_APP_DIR="$ROOT_DIR/apps/shared/ClawdbotKit/Tools/CanvasA2UI"
|
||||
|
||||
# Docker builds exclude vendor/apps via .dockerignore.
|
||||
# In that environment we must keep the prebuilt bundle.
|
||||
if [[ ! -d "$A2UI_RENDERER_DIR" || ! -d "$A2UI_APP_DIR" ]]; then
|
||||
echo "A2UI sources missing; keeping prebuilt bundle."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
INPUT_PATHS=(
|
||||
"$ROOT_DIR/package.json"
|
||||
"$ROOT_DIR/pnpm-lock.yaml"
|
||||
"$ROOT_DIR/vendor/a2ui/renderers/lit"
|
||||
"$ROOT_DIR/apps/shared/ClawdbotKit/Tools/CanvasA2UI"
|
||||
"$A2UI_RENDERER_DIR"
|
||||
"$A2UI_APP_DIR"
|
||||
)
|
||||
|
||||
collect_files() {
|
||||
local path
|
||||
for path in "${INPUT_PATHS[@]}"; do
|
||||
if [[ -d "$path" ]]; then
|
||||
find "$path" -type f -print0
|
||||
else
|
||||
printf '%s\0' "$path"
|
||||
fi
|
||||
done
|
||||
compute_hash() {
|
||||
ROOT_DIR="$ROOT_DIR" node --input-type=module - "${INPUT_PATHS[@]}" <<'NODE'
|
||||
import { createHash } from "node:crypto";
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const rootDir = process.env.ROOT_DIR ?? process.cwd();
|
||||
const inputs = process.argv.slice(2);
|
||||
const files = [];
|
||||
|
||||
async function walk(entryPath) {
|
||||
const st = await fs.stat(entryPath);
|
||||
if (st.isDirectory()) {
|
||||
const entries = await fs.readdir(entryPath);
|
||||
for (const entry of entries) {
|
||||
await walk(path.join(entryPath, entry));
|
||||
}
|
||||
return;
|
||||
}
|
||||
files.push(entryPath);
|
||||
}
|
||||
|
||||
compute_hash() {
|
||||
collect_files \
|
||||
| LC_ALL=C sort -z \
|
||||
| xargs -0 shasum -a 256 \
|
||||
| shasum -a 256 \
|
||||
| awk '{print $1}'
|
||||
for (const input of inputs) {
|
||||
await walk(input);
|
||||
}
|
||||
|
||||
function normalize(p) {
|
||||
return p.split(path.sep).join("/");
|
||||
}
|
||||
|
||||
files.sort((a, b) => normalize(a).localeCompare(normalize(b)));
|
||||
|
||||
const hash = createHash("sha256");
|
||||
for (const filePath of files) {
|
||||
const rel = normalize(path.relative(rootDir, filePath));
|
||||
hash.update(rel);
|
||||
hash.update("\0");
|
||||
hash.update(await fs.readFile(filePath));
|
||||
hash.update("\0");
|
||||
}
|
||||
|
||||
process.stdout.write(hash.digest("hex"));
|
||||
NODE
|
||||
}
|
||||
|
||||
current_hash="$(compute_hash)"
|
||||
@ -46,7 +81,7 @@ if [[ -f "$HASH_FILE" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
pnpm -s exec tsc -p vendor/a2ui/renderers/lit/tsconfig.json
|
||||
rolldown -c apps/shared/ClawdbotKit/Tools/CanvasA2UI/rolldown.config.mjs
|
||||
pnpm -s exec tsc -p "$A2UI_RENDERER_DIR/tsconfig.json"
|
||||
rolldown -c "$A2UI_APP_DIR/rolldown.config.mjs"
|
||||
|
||||
echo "$current_hash" > "$HASH_FILE"
|
||||
|
||||
@ -19,12 +19,17 @@ export async function copyA2uiAssets({
|
||||
srcDir: string;
|
||||
outDir: string;
|
||||
}) {
|
||||
const skipMissing = process.env.CLAWDBOT_A2UI_SKIP_MISSING === "1";
|
||||
try {
|
||||
await fs.stat(path.join(srcDir, "index.html"));
|
||||
await fs.stat(path.join(srcDir, "a2ui.bundle.js"));
|
||||
} catch (err) {
|
||||
const message =
|
||||
'Missing A2UI bundle assets. Run "pnpm canvas:a2ui:bundle" and retry.';
|
||||
if (skipMissing) {
|
||||
console.warn(`${message} Skipping copy (CLAWDBOT_A2UI_SKIP_MISSING=1).`);
|
||||
return;
|
||||
}
|
||||
throw new Error(message, { cause: err });
|
||||
}
|
||||
await fs.mkdir(path.dirname(outDir), { recursive: true });
|
||||
|
||||
@ -2,6 +2,16 @@
|
||||
set -euo pipefail
|
||||
|
||||
INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://molt.bot/install.sh}"
|
||||
DEFAULT_PACKAGE="moltbot"
|
||||
if [[ -z "${CLAWDBOT_INSTALL_PACKAGE:-}" && "$INSTALL_URL" == *"clawd.bot"* ]]; then
|
||||
DEFAULT_PACKAGE="clawdbot"
|
||||
fi
|
||||
PACKAGE_NAME="${CLAWDBOT_INSTALL_PACKAGE:-$DEFAULT_PACKAGE}"
|
||||
if [[ "$PACKAGE_NAME" == "moltbot" ]]; then
|
||||
ALT_PACKAGE_NAME="clawdbot"
|
||||
else
|
||||
ALT_PACKAGE_NAME="moltbot"
|
||||
fi
|
||||
|
||||
echo "==> Pre-flight: ensure git absent"
|
||||
if command -v git >/dev/null; then
|
||||
@ -18,26 +28,39 @@ export PATH="$HOME/.npm-global/bin:$PATH"
|
||||
echo "==> Verify git installed"
|
||||
command -v git >/dev/null
|
||||
|
||||
echo "==> Verify moltbot installed"
|
||||
EXPECTED_VERSION="${CLAWDBOT_INSTALL_EXPECT_VERSION:-}"
|
||||
if [[ -n "$EXPECTED_VERSION" ]]; then
|
||||
LATEST_VERSION="$EXPECTED_VERSION"
|
||||
else
|
||||
LATEST_VERSION="$(npm view moltbot version)"
|
||||
LATEST_VERSION="$(npm view "$PACKAGE_NAME" version)"
|
||||
fi
|
||||
CMD_PATH="$(command -v moltbot || true)"
|
||||
if [[ -z "$CMD_PATH" && -x "$HOME/.npm-global/bin/moltbot" ]]; then
|
||||
CMD_PATH="$HOME/.npm-global/bin/moltbot"
|
||||
CLI_NAME="$PACKAGE_NAME"
|
||||
CMD_PATH="$(command -v "$CLI_NAME" || true)"
|
||||
if [[ -z "$CMD_PATH" ]]; then
|
||||
CLI_NAME="$ALT_PACKAGE_NAME"
|
||||
CMD_PATH="$(command -v "$CLI_NAME" || true)"
|
||||
fi
|
||||
if [[ -z "$CMD_PATH" && -x "$HOME/.npm-global/bin/$PACKAGE_NAME" ]]; then
|
||||
CLI_NAME="$PACKAGE_NAME"
|
||||
CMD_PATH="$HOME/.npm-global/bin/$PACKAGE_NAME"
|
||||
fi
|
||||
if [[ -z "$CMD_PATH" && -x "$HOME/.npm-global/bin/$ALT_PACKAGE_NAME" ]]; then
|
||||
CLI_NAME="$ALT_PACKAGE_NAME"
|
||||
CMD_PATH="$HOME/.npm-global/bin/$ALT_PACKAGE_NAME"
|
||||
fi
|
||||
if [[ -z "$CMD_PATH" ]]; then
|
||||
echo "moltbot not on PATH" >&2
|
||||
echo "Neither $PACKAGE_NAME nor $ALT_PACKAGE_NAME is on PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$EXPECTED_VERSION" && "$CLI_NAME" != "$PACKAGE_NAME" ]]; then
|
||||
LATEST_VERSION="$(npm view "$CLI_NAME" version)"
|
||||
fi
|
||||
echo "==> Verify CLI installed: $CLI_NAME"
|
||||
INSTALLED_VERSION="$("$CMD_PATH" --version 2>/dev/null | head -n 1 | tr -d '\r')"
|
||||
|
||||
echo "installed=$INSTALLED_VERSION expected=$LATEST_VERSION"
|
||||
echo "cli=$CLI_NAME installed=$INSTALLED_VERSION expected=$LATEST_VERSION"
|
||||
if [[ "$INSTALLED_VERSION" != "$LATEST_VERSION" ]]; then
|
||||
echo "ERROR: expected moltbot@$LATEST_VERSION, got @$INSTALLED_VERSION" >&2
|
||||
echo "ERROR: expected ${CLI_NAME}@${LATEST_VERSION}, got ${CLI_NAME}@${INSTALLED_VERSION}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@ -4,15 +4,26 @@ set -euo pipefail
|
||||
INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://molt.bot/install.sh}"
|
||||
SMOKE_PREVIOUS_VERSION="${CLAWDBOT_INSTALL_SMOKE_PREVIOUS:-}"
|
||||
SKIP_PREVIOUS="${CLAWDBOT_INSTALL_SMOKE_SKIP_PREVIOUS:-0}"
|
||||
DEFAULT_PACKAGE="moltbot"
|
||||
if [[ -z "${CLAWDBOT_INSTALL_PACKAGE:-}" && "$INSTALL_URL" == *"clawd.bot"* ]]; then
|
||||
DEFAULT_PACKAGE="clawdbot"
|
||||
fi
|
||||
PACKAGE_NAME="${CLAWDBOT_INSTALL_PACKAGE:-$DEFAULT_PACKAGE}"
|
||||
if [[ "$PACKAGE_NAME" == "moltbot" ]]; then
|
||||
ALT_PACKAGE_NAME="clawdbot"
|
||||
else
|
||||
ALT_PACKAGE_NAME="moltbot"
|
||||
fi
|
||||
|
||||
echo "==> Resolve npm versions"
|
||||
LATEST_VERSION="$(npm view "$PACKAGE_NAME" version)"
|
||||
if [[ -n "$SMOKE_PREVIOUS_VERSION" ]]; then
|
||||
LATEST_VERSION="$(npm view moltbot version)"
|
||||
PREVIOUS_VERSION="$SMOKE_PREVIOUS_VERSION"
|
||||
else
|
||||
VERSIONS_JSON="$(npm view moltbot versions --json)"
|
||||
versions_line="$(node - <<'NODE'
|
||||
VERSIONS_JSON="$(npm view "$PACKAGE_NAME" versions --json)"
|
||||
PREVIOUS_VERSION="$(VERSIONS_JSON="$VERSIONS_JSON" LATEST_VERSION="$LATEST_VERSION" node - <<'NODE'
|
||||
const raw = process.env.VERSIONS_JSON || "[]";
|
||||
const latest = process.env.LATEST_VERSION || "";
|
||||
let versions;
|
||||
try {
|
||||
versions = JSON.parse(raw);
|
||||
@ -25,41 +36,52 @@ if (!Array.isArray(versions)) {
|
||||
if (versions.length === 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
const latest = versions[versions.length - 1];
|
||||
const previous = versions.length >= 2 ? versions[versions.length - 2] : latest;
|
||||
process.stdout.write(`${latest} ${previous}`);
|
||||
const latestIndex = latest ? versions.lastIndexOf(latest) : -1;
|
||||
if (latestIndex > 0) {
|
||||
process.stdout.write(String(versions[latestIndex - 1]));
|
||||
process.exit(0);
|
||||
}
|
||||
process.stdout.write(String(latest || versions[versions.length - 1]));
|
||||
NODE
|
||||
)"
|
||||
LATEST_VERSION="${versions_line%% *}"
|
||||
PREVIOUS_VERSION="${versions_line#* }"
|
||||
fi
|
||||
|
||||
if [[ -n "${CLAWDBOT_INSTALL_LATEST_OUT:-}" ]]; then
|
||||
printf "%s" "$LATEST_VERSION" > "$CLAWDBOT_INSTALL_LATEST_OUT"
|
||||
fi
|
||||
|
||||
echo "latest=$LATEST_VERSION previous=$PREVIOUS_VERSION"
|
||||
echo "package=$PACKAGE_NAME latest=$LATEST_VERSION previous=$PREVIOUS_VERSION"
|
||||
|
||||
if [[ "$SKIP_PREVIOUS" == "1" ]]; then
|
||||
echo "==> Skip preinstall previous (CLAWDBOT_INSTALL_SMOKE_SKIP_PREVIOUS=1)"
|
||||
else
|
||||
echo "==> Preinstall previous (forces installer upgrade path)"
|
||||
npm install -g "moltbot@${PREVIOUS_VERSION}"
|
||||
npm install -g "${PACKAGE_NAME}@${PREVIOUS_VERSION}"
|
||||
fi
|
||||
|
||||
echo "==> Run official installer one-liner"
|
||||
curl -fsSL "$INSTALL_URL" | bash
|
||||
|
||||
echo "==> Verify installed version"
|
||||
INSTALLED_VERSION="$(moltbot --version 2>/dev/null | head -n 1 | tr -d '\r')"
|
||||
echo "installed=$INSTALLED_VERSION expected=$LATEST_VERSION"
|
||||
CLI_NAME="$PACKAGE_NAME"
|
||||
if ! command -v "$CLI_NAME" >/dev/null 2>&1; then
|
||||
if command -v "$ALT_PACKAGE_NAME" >/dev/null 2>&1; then
|
||||
CLI_NAME="$ALT_PACKAGE_NAME"
|
||||
LATEST_VERSION="$(npm view "$CLI_NAME" version)"
|
||||
echo "==> Detected alternate CLI: $CLI_NAME"
|
||||
else
|
||||
echo "ERROR: neither $PACKAGE_NAME nor $ALT_PACKAGE_NAME is on PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if [[ -n "${CLAWDBOT_INSTALL_LATEST_OUT:-}" ]]; then
|
||||
printf "%s" "$LATEST_VERSION" > "$CLAWDBOT_INSTALL_LATEST_OUT"
|
||||
fi
|
||||
INSTALLED_VERSION="$("$CLI_NAME" --version 2>/dev/null | head -n 1 | tr -d '\r')"
|
||||
echo "cli=$CLI_NAME installed=$INSTALLED_VERSION expected=$LATEST_VERSION"
|
||||
|
||||
if [[ "$INSTALLED_VERSION" != "$LATEST_VERSION" ]]; then
|
||||
echo "ERROR: expected moltbot@$LATEST_VERSION, got moltbot@$INSTALLED_VERSION" >&2
|
||||
echo "ERROR: expected ${CLI_NAME}@${LATEST_VERSION}, got ${CLI_NAME}@${INSTALLED_VERSION}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Sanity: CLI runs"
|
||||
moltbot --help >/dev/null
|
||||
"$CLI_NAME" --help >/dev/null
|
||||
|
||||
echo "OK"
|
||||
|
||||
@ -18,16 +18,21 @@ const runs = [
|
||||
},
|
||||
];
|
||||
|
||||
const parallelRuns = runs.filter((entry) => entry.name !== "gateway");
|
||||
const serialRuns = runs.filter((entry) => entry.name === "gateway");
|
||||
|
||||
const children = new Set();
|
||||
const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
|
||||
const isMacOS = process.platform === "darwin" || process.env.RUNNER_OS === "macOS";
|
||||
const isWindows = process.platform === "win32" || process.env.RUNNER_OS === "Windows";
|
||||
const isWindowsCi = isCI && isWindows;
|
||||
const shardOverride = Number.parseInt(process.env.CLAWDBOT_TEST_SHARDS ?? "", 10);
|
||||
const shardCount = isWindowsCi ? (Number.isFinite(shardOverride) && shardOverride > 1 ? shardOverride : 2) : 1;
|
||||
const windowsCiArgs = isWindowsCi ? ["--no-file-parallelism", "--dangerouslyIgnoreUnhandledErrors"] : [];
|
||||
const overrideWorkers = Number.parseInt(process.env.CLAWDBOT_TEST_WORKERS ?? "", 10);
|
||||
const resolvedOverride = Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null;
|
||||
const parallelRuns = isWindowsCi ? [] : runs.filter((entry) => entry.name !== "gateway");
|
||||
const serialRuns = isWindowsCi ? runs : runs.filter((entry) => entry.name === "gateway");
|
||||
const localWorkers = Math.max(4, Math.min(16, os.cpus().length));
|
||||
const perRunWorkers = Math.max(1, Math.floor(localWorkers / parallelRuns.length));
|
||||
const parallelCount = Math.max(1, parallelRuns.length);
|
||||
const perRunWorkers = Math.max(1, Math.floor(localWorkers / parallelCount));
|
||||
const macCiWorkers = isCI && isMacOS ? 1 : perRunWorkers;
|
||||
// Keep worker counts predictable for local runs; trim macOS CI workers to avoid worker crashes/OOM.
|
||||
// In CI on linux/windows, prefer Vitest defaults to avoid cross-test interference from lower worker counts.
|
||||
@ -39,9 +44,11 @@ const WARNING_SUPPRESSION_FLAGS = [
|
||||
"--disable-warning=DEP0060",
|
||||
];
|
||||
|
||||
const run = (entry) =>
|
||||
const runOnce = (entry, extraArgs = []) =>
|
||||
new Promise((resolve) => {
|
||||
const args = maxWorkers ? [...entry.args, "--maxWorkers", String(maxWorkers)] : entry.args;
|
||||
const args = maxWorkers
|
||||
? [...entry.args, "--maxWorkers", String(maxWorkers), ...windowsCiArgs, ...extraArgs]
|
||||
: [...entry.args, ...windowsCiArgs, ...extraArgs];
|
||||
const nodeOptions = process.env.NODE_OPTIONS ?? "";
|
||||
const nextNodeOptions = WARNING_SUPPRESSION_FLAGS.reduce(
|
||||
(acc, flag) => (acc.includes(flag) ? acc : `${acc} ${flag}`.trim()),
|
||||
@ -59,6 +66,16 @@ const run = (entry) =>
|
||||
});
|
||||
});
|
||||
|
||||
const run = async (entry) => {
|
||||
if (shardCount <= 1) return runOnce(entry);
|
||||
for (let shardIndex = 1; shardIndex <= shardCount; shardIndex += 1) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const code = await runOnce(entry, ["--shard", `${shardIndex}/${shardCount}`]);
|
||||
if (code !== 0) return code;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
const shutdown = (signal) => {
|
||||
for (const child of children) {
|
||||
child.kill(signal);
|
||||
|
||||
@ -1 +1 @@
|
||||
1376c2e99ad07193d9ab1719200675d84ffb40db417d05128cf07c3b8283581e
|
||||
b6d3dea7c656c8a480059c32e954c4d39053ff79c4e9c69b38f4c04e3f0280d4
|
||||
|
||||
@ -66,20 +66,23 @@ export async function maybeInstallDaemon(params: {
|
||||
|
||||
if (shouldInstall) {
|
||||
let installError: string | null = null;
|
||||
if (!params.daemonRuntime) {
|
||||
if (GATEWAY_DAEMON_RUNTIME_OPTIONS.length === 1) {
|
||||
daemonRuntime = GATEWAY_DAEMON_RUNTIME_OPTIONS[0]?.value ?? DEFAULT_GATEWAY_DAEMON_RUNTIME;
|
||||
} else {
|
||||
daemonRuntime = guardCancel(
|
||||
await select({
|
||||
message: "Gateway service runtime",
|
||||
options: GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
||||
initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||
}),
|
||||
params.runtime,
|
||||
) as GatewayDaemonRuntime;
|
||||
}
|
||||
}
|
||||
await withProgress(
|
||||
{ label: "Gateway service", indeterminate: true, delayMs: 0 },
|
||||
async (progress) => {
|
||||
if (!params.daemonRuntime) {
|
||||
daemonRuntime = guardCancel(
|
||||
await select({
|
||||
message: "Gateway service runtime",
|
||||
options: GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
||||
initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||
}),
|
||||
params.runtime,
|
||||
) as GatewayDaemonRuntime;
|
||||
}
|
||||
|
||||
progress.setLabel("Preparing Gateway service…");
|
||||
|
||||
const cfg = loadConfig();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user