diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8cc86bd63..885d87fcb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/install-smoke.yml b/.github/workflows/install-smoke.yml index 16eba4eed..0347c7810 100644 --- a/.github/workflows/install-smoke.yml +++ b/.github/workflows/install-smoke.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 079c32533..ce4114902 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/Dockerfile b/Dockerfile index 642cfd612..9c6aa7036 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/README.md b/README.md index 9f1f93193..e19a98a66 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# 🦞 Clawdbot — Personal AI Assistant +# 🦞 Moltbot — Personal AI Assistant
-
+
@@ -9,21 +9,21 @@
-**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` (then the sender is added to a local allowlist store).
+- Approve with: `moltbot pairing approve ` (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
diff --git a/package.json b/package.json
index d9c25c8b0..f91af8199 100644
--- a/package.json
+++ b/package.json
@@ -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/**",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 82f0a96e2..4f35c2612 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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:
diff --git a/scripts/bundle-a2ui.sh b/scripts/bundle-a2ui.sh
index a1cf7ff2b..75844ec6d 100755
--- a/scripts/bundle-a2ui.sh
+++ b/scripts/bundle-a2ui.sh
@@ -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"
diff --git a/scripts/canvas-a2ui-copy.ts b/scripts/canvas-a2ui-copy.ts
index b8a80675f..e95be5fdd 100644
--- a/scripts/canvas-a2ui-copy.ts
+++ b/scripts/canvas-a2ui-copy.ts
@@ -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 });
diff --git a/scripts/docker/install-sh-nonroot/run.sh b/scripts/docker/install-sh-nonroot/run.sh
index a689498e0..58a8fc3e7 100644
--- a/scripts/docker/install-sh-nonroot/run.sh
+++ b/scripts/docker/install-sh-nonroot/run.sh
@@ -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
diff --git a/scripts/docker/install-sh-smoke/run.sh b/scripts/docker/install-sh-smoke/run.sh
index 9c8870b2b..09a597de8 100755
--- a/scripts/docker/install-sh-smoke/run.sh
+++ b/scripts/docker/install-sh-smoke/run.sh
@@ -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"
diff --git a/scripts/test-parallel.mjs b/scripts/test-parallel.mjs
index 242b444ff..811d7c546 100644
--- a/scripts/test-parallel.mjs
+++ b/scripts/test-parallel.mjs
@@ -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);
diff --git a/src/canvas-host/a2ui/.bundle.hash b/src/canvas-host/a2ui/.bundle.hash
index 6ca31cbe4..6c9cb0299 100644
--- a/src/canvas-host/a2ui/.bundle.hash
+++ b/src/canvas-host/a2ui/.bundle.hash
@@ -1 +1 @@
-1376c2e99ad07193d9ab1719200675d84ffb40db417d05128cf07c3b8283581e
+b6d3dea7c656c8a480059c32e954c4d39053ff79c4e9c69b38f4c04e3f0280d4
diff --git a/src/commands/configure.daemon.ts b/src/commands/configure.daemon.ts
index 38d8365c0..c0431c9f1 100644
--- a/src/commands/configure.daemon.ts
+++ b/src/commands/configure.daemon.ts
@@ -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();