From 34c3e90156b64e932948119815c2a9a658b3d5ed Mon Sep 17 00:00:00 2001 From: "exe.dev user" Date: Mon, 4 May 2026 09:01:43 +0000 Subject: [PATCH 1/4] feat(setup): clarify @BotFather is Telegram's official bot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 1 of the Telegram channel's BotFather instructions used to read: 1. Open Telegram and message @BotFather Two small UX issues with that: - "BotFather" reads slightly sketchy without context — a first-time user has no way to know it's the official, sanctioned account rather than an impersonator. - Typing the username from memory leaves room for picking a typo'd impostor account (Telegram has many @BotF4ther / @BotFAther / etc. look-alikes). Update the line so the official-bot framing is part of the instruction itself: 1. Open Telegram and message @BotFather — Telegram's official bot for creating and managing bots One-line change in the existing note() body. No new dependencies, no asset churn, no other behavior change. --- setup/channels/telegram.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/channels/telegram.ts b/setup/channels/telegram.ts index 41ee407..bf474f2 100644 --- a/setup/channels/telegram.ts +++ b/setup/channels/telegram.ts @@ -149,7 +149,7 @@ async function collectTelegramToken(): Promise { "Your assistant talks to you through a Telegram bot you create.", "Here's how:", '', - ' 1. Open Telegram and message @BotFather', + " 1. Open Telegram and message @BotFather — Telegram's official bot for creating and managing bots", ' 2. Send /newbot and follow the prompts', ' 3. Copy the token it gives you (it looks like :)', '', From b33f6654fdece99c5b11e06fc160917f1ac6fb61 Mon Sep 17 00:00:00 2001 From: "exe.dev user" Date: Mon, 4 May 2026 09:23:43 +0000 Subject: [PATCH 2/4] fix(setup): use fmtDuration in the container-build spinner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setup/lib/windowed-runner.ts was the one place on main still printing elapsed time as raw seconds (`(170s)`) instead of using the minute-aware `fmtDuration` helper from #2108. Two spots — the live spinner suffix that ticks during the build, and the success/error completion suffix — both now go through `fmtDuration`, so anything past 60 seconds renders as `Xm Ys` (e.g. `2m 50s`) like the rest of the setup flow. The miss happened because a separate PR (closed) was supposed to remove the timer entirely from this file, so #2108 deliberately skipped it. With that other PR closed, applying `fmtDuration` here is the consistent fix. Pure formatting change. The helper itself is unchanged from #2108; behavior under 60s is identical (`Xs`); behavior past 60s now matches everywhere else. --- setup/lib/windowed-runner.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/setup/lib/windowed-runner.ts b/setup/lib/windowed-runner.ts index 6f165a4..87c971e 100644 --- a/setup/lib/windowed-runner.ts +++ b/setup/lib/windowed-runner.ts @@ -23,7 +23,7 @@ import { emit as phEmit } from './diagnostics.js'; import type { StepResult, SpinnerLabels } from './runner.js'; import { dumpTranscriptOnFailure, spawnStep, writeStepEntry } from './runner.js'; import * as setupLog from '../logs.js'; -import { brandBody, fitToWidth } from './theme.js'; +import { brandBody, fitToWidth, fmtDuration } from './theme.js'; const WINDOW_SIZE = 3; const SPINNER_FRAMES = ['◒', '◐', '◓', '◑']; @@ -85,9 +85,8 @@ async function runUnderWindow( const redraw = (): void => { if (stallPromptActive) return; out.write(`\x1b[${WINDOW_SIZE + 1}A`); - const elapsed = Math.round((Date.now() - start) / 1000); const icon = SPINNER_FRAMES[frameIdx % SPINNER_FRAMES.length]; - const suffix = ` (${elapsed}s)`; + const suffix = ` (${fmtDuration(Date.now() - start)})`; const header = fitToWidth(labels.running, suffix); out.write(`\x1b[2K${k.cyan(icon)} ${header}${k.dim(suffix)}\n`); @@ -164,8 +163,7 @@ async function runUnderWindow( out.write(SHOW_CURSOR); process.off('exit', restoreCursorOnExit); - const elapsed = Math.round((Date.now() - start) / 1000); - const suffix = ` (${elapsed}s)`; + const suffix = ` (${fmtDuration(Date.now() - start)})`; if (result.ok) { const isSkipped = result.terminal?.fields.STATUS === 'skipped'; const msg = isSkipped && labels.skipped ? labels.skipped : labels.done; From 9e4feb08001c8901aa41ddf2ce1ec7e0b47331df Mon Sep 17 00:00:00 2001 From: koshkoshinsk Date: Mon, 4 May 2026 12:54:54 +0000 Subject: [PATCH 3/4] feat(setup): warn when host is below recommended hardware specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-flight check in nanoclaw.sh that detects available RAM and free disk on the project-root partition (Linux + macOS) before the bootstrap spinner runs. Below 3700 MB RAM or 20 GB free disk, surfaces a "likely cannot run" warning with a Try-anyway prompt defaulting to abort. The 3700 MB floor sits below 4 GB because "4 GB" VMs typically report 3700–3900 MB after kernel reserves (Hetzner CX21 ≈ 3814, AWS t3.medium ≈ 3800). Cheaper to fail here than to wait through pnpm install on a host that can't run the agent container. Diagnostic events fire on continue/abort. Co-Authored-By: Claude Opus 4.7 (1M context) --- nanoclaw.sh | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/nanoclaw.sh b/nanoclaw.sh index 82d445a..c2b2614 100755 --- a/nanoclaw.sh +++ b/nanoclaw.sh @@ -137,6 +137,69 @@ write_header # NANOCLAW_BOOTSTRAPPED=1 and skips re-printing the wordmark. cat "$PROJECT_ROOT/assets/setup-splash.txt" +# ─── pre-flight: minimum hardware specs ──────────────────────────────── +# NanoClaw runs an agent container per session. Below these thresholds the +# host + container + agent will struggle (OOM under load, image + session +# DBs filling the disk). Soft warn — `df` only sees the partition that +# $PROJECT_ROOT lives on, which can underreport on hosts with separate +# /home or /var mounts, so the user can override. + +# RAM floor is set below 4 GB because "4 GB" VMs typically report 3700–3900 MB +# after kernel reserves (e.g. Hetzner CX21 ≈ 3814, AWS t3.medium ≈ 3800). +MIN_MEM_MB=3700 +MIN_DISK_GB=20 + +detect_mem_mb() { + case "$(uname -s)" in + Linux) + awk '/^MemTotal:/ {printf "%d", $2 / 1024}' /proc/meminfo 2>/dev/null + ;; + Darwin) + local bytes + bytes=$(sysctl -n hw.memsize 2>/dev/null || echo 0) + echo $(( bytes / 1024 / 1024 )) + ;; + esac +} + +detect_disk_gb() { + # -P: POSIX format (no line-wrapping); -k: 1024-byte blocks. Avail is col 4. + df -Pk "$PROJECT_ROOT" 2>/dev/null \ + | awk 'NR==2 { printf "%d", $4 / 1024 / 1024 }' +} + +MEM_MB=$(detect_mem_mb) +DISK_GB=$(detect_disk_gb) +: "${MEM_MB:=0}" +: "${DISK_GB:=0}" + +LOW_MEM=false; LOW_DISK=false +[ "$MEM_MB" -gt 0 ] && [ "$MEM_MB" -lt "$MIN_MEM_MB" ] && LOW_MEM=true +[ "$DISK_GB" -gt 0 ] && [ "$DISK_GB" -lt "$MIN_DISK_GB" ] && LOW_DISK=true + +if [ "$LOW_MEM" = true ] || [ "$LOW_DISK" = true ]; then + printf ' %s\n' "$(red 'Warning: this machine likely cannot run NanoClaw.')" + printf ' %s\n' "$(dim 'NanoClaw recommends a 4 GB+ machine with 20 GB+ free disk. Below this,')" + printf ' %s\n' "$(dim 'the host + agent container will run out of memory or disk under most')" + printf ' %s\n' "$(dim 'workloads. A stronger machine is strongly recommended.')" + [ "$LOW_MEM" = true ] && printf ' %s\n' "$(dim " · Detected RAM: ${MEM_MB} MB")" + [ "$LOW_DISK" = true ] && printf ' %s\n' "$(dim " · Free disk on $PROJECT_ROOT: ${DISK_GB} GB")" + printf '\n' + read -r -p " $(bold 'Try anyway?') [y/N] " SPECS_ANS Date: Mon, 4 May 2026 14:27:07 +0000 Subject: [PATCH 4/4] feat(setup): warn when running on a Google Compute Engine VM NanoClaw is known to not run reliably on GCE instances. Detect via DMI during pre-flight (between the spec check and root warning) and let the user abort before sinking time into bootstrap. Co-Authored-By: Claude Opus 4.7 (1M context) --- nanoclaw.sh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/nanoclaw.sh b/nanoclaw.sh index c2b2614..c17966e 100755 --- a/nanoclaw.sh +++ b/nanoclaw.sh @@ -200,6 +200,33 @@ if [ "$LOW_MEM" = true ] || [ "$LOW_DISK" = true ]; then esac fi +# ─── pre-flight: Google Cloud VM warning (Linux) ────────────────────── +# NanoClaw is known to not run reliably on Google Compute Engine instances. +# Warn early — before the root check or bootstrap spinner — so users can +# switch providers before sinking time into setup. Detection uses DMI +# (no network round-trip), which on GCE reports "Google" / "Google +# Compute Engine". +if [ "$(uname -s)" = "Linux" ] \ + && { grep -qi 'Google' /sys/class/dmi/id/product_name 2>/dev/null \ + || grep -qi 'Google' /sys/class/dmi/id/sys_vendor 2>/dev/null; }; then + printf ' %s\n' "$(red 'Warning: Google Cloud VM detected.')" + printf ' %s\n' "$(dim 'Google blocks sudo commands, so NanoClaw is unlikely to run successfully on this VM.')" + printf ' %s\n\n' "$(dim 'If you want to run NanoClaw successfully, switch to a different provider (Hetzner, Hostinger, exe.dev and others..).')" + read -r -p " $(bold 'Try anyway?') [y/N] " GCE_ANS