From 7300fec1e214b968f72ced8133b7bb1245cccc47 Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Tue, 23 Jun 2026 10:49:38 +0200 Subject: [PATCH] 0.12.0: hw-probe + model fixes + mother MCP infra Combined from three feature branches: - feature/hw-probe-agent-bootstrap: JSON hardware probe (clawdie-hw-probe), remove desktop icon, update START-HERE.txt - chore/0.12.0-model-fix-bump: deepseek-v4-pro model names, version 0.12.0 - feature/mother-mcp-infra: build-colibri.sh MCP tool, colibri-mcp-ssh wrapper --- TESTING.md | 2 - build.cfg | 2 +- build.sh | 9 +- live/operator-session/START-HERE.txt | 5 +- live/operator-session/clawdie-hw-probe | 288 ++++++++++++++++++ .../clawdie-live-seed.README.txt | 2 +- live/operator-session/hw-report.desktop | 8 - packaging/mother/build-colibri.sh | 68 +++++ packaging/mother/colibri-mcp-ssh | 6 + scripts/stage-colibri-iso.sh | 2 +- 10 files changed, 369 insertions(+), 23 deletions(-) create mode 100755 live/operator-session/clawdie-hw-probe delete mode 100644 live/operator-session/hw-report.desktop create mode 100755 packaging/mother/build-colibri.sh create mode 100755 packaging/mother/colibri-mcp-ssh diff --git a/TESTING.md b/TESTING.md index a9060fd..2b0da29 100644 --- a/TESTING.md +++ b/TESTING.md @@ -216,8 +216,6 @@ test -x /mnt/usr/local/bin/startx test -x /mnt/usr/local/bin/clawdie-startx test -x /mnt/usr/local/bin/clawdie-gui test -x /mnt/usr/local/bin/hw-report -test -f "/mnt/usr/local/share/applications/Clawdie Hardware Report.desktop" -test -f "/mnt/home/clawdie/Desktop/Clawdie Hardware Report.desktop" test -x /mnt/home/clawdie/.xinitrc test -x /mnt/home/clawdie/.config/xfce4/xinitrc pkg -r /mnt info -e xterm diff --git a/build.cfg b/build.cfg index fe6375e..b00be04 100644 --- a/build.cfg +++ b/build.cfg @@ -13,7 +13,7 @@ FREEBSD_MEMSTICK_SHA256_URL="${FREEBSD_ISO_BASE_URL}/CHECKSUM.SHA256-FreeBSD-${F # clawdie-iso and colibri share this unified version (keep colibri's Cargo.toml # in sync). zot and clawdie-ai keep their own versions, recorded as provenance in # build-manifest.json. Bump this for each release. -ISO_VERSION="${ISO_VERSION:-0.11.0}" +ISO_VERSION="${ISO_VERSION:-0.12.0}" BUILD_CHANNEL="${BUILD_CHANNEL:-dev}" # dev | release # Output image diff --git a/build.sh b/build.sh index 9bafc23..903c0ab 100755 --- a/build.sh +++ b/build.sh @@ -1308,6 +1308,8 @@ configure_live_operator_session() { "${MOUNT_POINT}/usr/local/bin/clawdie-noblank-guard.sh" install -m 0755 "${LIVE_SESSION_DIR}/hw-report" \ "${MOUNT_POINT}/usr/local/bin/hw-report" + install -m 0755 "${LIVE_SESSION_DIR}/clawdie-hw-probe" \ + "${MOUNT_POINT}/usr/local/bin/clawdie-hw-probe" # Vaultwarden secret bridge (bw -> .env). Always staged: needs only the # bundled `bw` CLI and a 0600 bootstrap drop; absent bootstrap = no-op, so # the manual setup wizard stays the floor. See docs/VAULTWARDEN-SETUP.md. @@ -1711,15 +1713,11 @@ EOF "${MOUNT_POINT}/usr/local/share/applications/Clawdie Start Here.desktop" install -m 0644 "${LIVE_SESSION_DIR}/colibri-dashboard.desktop" \ "${MOUNT_POINT}/usr/local/share/applications/Colibri Dashboard.desktop" - install -m 0644 "${LIVE_SESSION_DIR}/hw-report.desktop" \ - "${MOUNT_POINT}/usr/local/share/applications/Clawdie Hardware Report.desktop" mkdir -p "${MOUNT_POINT}/home/clawdie/Desktop" install -m 0644 "${LIVE_SESSION_DIR}/clawdie-bootstrap.desktop" \ "${MOUNT_POINT}/home/clawdie/Desktop/Clawdie Start Here.desktop" install -m 0644 "${LIVE_SESSION_DIR}/colibri-dashboard.desktop" \ "${MOUNT_POINT}/home/clawdie/Desktop/Colibri Dashboard.desktop" - install -m 0644 "${LIVE_SESSION_DIR}/hw-report.desktop" \ - "${MOUNT_POINT}/home/clawdie/Desktop/Clawdie Hardware Report.desktop" mkdir -p "${MOUNT_POINT}/usr/local/share/clawdie-iso" install -m 0644 "${LIVE_SESSION_DIR}/START-HERE.txt" \ "${MOUNT_POINT}/usr/local/share/clawdie-iso/START-HERE.txt" @@ -1756,8 +1754,7 @@ EOF chmod 0755 "${MOUNT_POINT}/home/clawdie/Desktop" chmod 0644 \ "${MOUNT_POINT}/home/clawdie/Desktop/Clawdie Start Here.desktop" \ - "${MOUNT_POINT}/home/clawdie/Desktop/Colibri Dashboard.desktop" \ - "${MOUNT_POINT}/home/clawdie/Desktop/Clawdie Hardware Report.desktop" + "${MOUNT_POINT}/home/clawdie/Desktop/Colibri Dashboard.desktop" install_live_ai_source_snapshots diff --git a/live/operator-session/START-HERE.txt b/live/operator-session/START-HERE.txt index eda097b..d6b8c09 100644 --- a/live/operator-session/START-HERE.txt +++ b/live/operator-session/START-HERE.txt @@ -9,7 +9,7 @@ FIRST ACTIONS 1. Save any notes you want in this file or close it. 2. Open "Colibri Dashboard" on the desktop when you are ready. -3. Use "Clawdie Hardware Report" if this machine needs diagnostics. +3. Run 'mdo -u root hw-report' in a terminal if this machine needs diagnostics. COLIBRI ------- @@ -75,9 +75,6 @@ Open manually: HARDWARE REPORT --------------- -Desktop launcher: - Clawdie Hardware Report - Terminal: mdo -u root hw-report diff --git a/live/operator-session/clawdie-hw-probe b/live/operator-session/clawdie-hw-probe new file mode 100755 index 0000000..fb4e8cb --- /dev/null +++ b/live/operator-session/clawdie-hw-probe @@ -0,0 +1,288 @@ +#!/bin/sh +# clawdie-hw-probe — single-shot JSON hardware probe +# +# Outputs one JSON object to stdout. All messages/errors go to stderr. +# Designed to run as root for full hardware access. +# Target: < 3 seconds on cold cache. +# +# Schema: clawdie.hw-probe.v1 + +set -u +PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +export PATH + +######################################################################## +# helpers +######################################################################## + +die() { + printf '{"error":"%s","schema_version":"clawdie.hw-probe.v1"}\n' "$(json_escape "$1")" + exit 1 +} + +json_escape() { + printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\n/\\n/g; s/\r//g; s/\t/\\t/g' +} + +json_bool() { + if [ "$1" = "yes" ] || [ "$1" = "YES" ] || [ "$1" = "true" ] || [ "$1" = "TRUE" ] || [ "$1" = "1" ]; then + printf 'true' + else + printf 'false' + fi +} + +######################################################################## +# field collectors — each function prints to a temp file or variable +######################################################################## + +collect_hostname() { + hostname 2>/dev/null || echo "unknown" +} + +collect_freebsd_version() { + # kernel, userland, runtime + _k="$(freebsd-version -k 2>/dev/null || echo "unknown")" + _u="$(freebsd-version -u 2>/dev/null || echo "unknown")" + _r="$(freebsd-version -r 2>/dev/null || echo "unknown")" + printf '{"kernel":"%s","userland":"%s","runtime":"%s"}' \ + "$(json_escape "$_k")" \ + "$(json_escape "$_u")" \ + "$(json_escape "$_r")" +} + +collect_ram_gb() { + _bytes="$(sysctl -n hw.physmem 2>/dev/null || echo 0)" + # Round to nearest GB: add 0.5 GB then truncate + _half_gb=536870912 # 2^29 + _gb=$(( ( _bytes + _half_gb ) / 1073741824 )) + printf '%d' "$_gb" +} + +collect_cpu_model() { + sysctl -n hw.model 2>/dev/null || echo "unknown" +} + +collect_cpu_cores() { + sysctl -n hw.ncpu 2>/dev/null || echo "0" +} + +collect_gpu() { + # Extract VGA/display devices from pciconf -lv + # Output: JSON array of {vendor, device, chip} + _found=0 + printf '[' + pciconf -lv 2>/dev/null | awk ' + BEGIN { in_block=0; vendor=""; device=""; chip=""; first=1 } + /^[a-z]/ { + if (in_block && device ~ /VGA|display/) { + if (!first) printf ","; + gsub(/"/, "\\\"", vendor); + gsub(/"/, "\\\"", device); + gsub(/"/, "\\\"", chip); + printf "{\"vendor\":\"%s\",\"device\":\"%s\",\"chip\":\"%s\"}", vendor, device, chip; + first=0; + } + in_block=1; vendor=""; device=""; chip=$0; + sub(/@.*/, "", chip); + } + /vendor *=/ { vendor=$0; sub(/^[[:space:]]*vendor[[:space:]]*=[[:space:]]*/, "", vendor); sub(/'\''/, "", vendor); sub(/'\''$/, "", vendor) } + /device *=/ { device=$0; sub(/^[[:space:]]*device[[:space:]]*=[[:space:]]*/, "", device); sub(/'\''/, "", device); sub(/'\''$/, "", device) } + END { + if (in_block && device ~ /VGA|display/) { + if (!first) printf ","; + gsub(/"/, "\\\"", vendor); + gsub(/"/, "\\\"", device); + gsub(/"/, "\\\"", chip); + printf "{\"vendor\":\"%s\",\"device\":\"%s\",\"chip\":\"%s\"}", vendor, device, chip; + } + } + ' + printf ']' +} + +collect_gpu_driver() { + # Check which KMS drivers are loaded + printf '[' + _first=1 + for _drv in amdgpu i915kms nvidia-modeset; do + if kldstat -q -n "$_drv" 2>/dev/null; then + if [ "$_first" -ne 1 ]; then printf ','; fi + printf '"%s"' "$_drv" + _first=0 + fi + done + printf ']' +} + +collect_disks() { + # Disk names + sizes + printf '{' + _disks="$(sysctl -n kern.disks 2>/dev/null || true)" + _first=1 + for _disk in $_disks; do + _size="" + # Try geom disk list first (fast, no device open) + if command -v geom >/dev/null 2>&1; then + _size="$(geom disk list "$_disk" 2>/dev/null | awk '/Mediasize:/ {print $2}' | head -1)" + fi + # Fallback to camcontrol + if [ -z "$_size" ] && command -v camcontrol >/dev/null 2>&1; then + _size="$(camcontrol identify "/dev/${_disk}" 2>/dev/null | awk -F'[<>]' '/LBA48 supported|LBA supported/{print $2}' | head -1)" + fi + # Fallback to diskinfo + if [ -z "$_size" ] && command -v diskinfo >/dev/null 2>&1; then + _size="$(diskinfo "/dev/${_disk}" 2>/dev/null | awk '{print $3}' | head -1)" + fi + if [ "$_first" -ne 1 ]; then printf ','; fi + printf '"%s":%s' "$(json_escape "$_disk")" "${_size:-0}" + _first=0 + done + printf '}' +} + +collect_network_interfaces() { + # Simpler, reliable version that doesn't rely on subshell variable leakage + _ifaces="$(ifconfig -l 2>/dev/null)" + printf '{' + _first=1 + for _iface in $_ifaces; do + _mac="$(ifconfig "$_iface" 2>/dev/null | awk '/ether/{print $2}' | head -1)" + [ -z "$_mac" ] && continue + if [ "$_first" -ne 1 ]; then printf ','; fi + printf '"%s":"%s"' "$(json_escape "$_iface")" "$(json_escape "$_mac")" + _first=0 + done + printf '}' +} + +collect_wifi() { + _wlan="$(sysctl -n net.wlan.devices 2>/dev/null || true)" + if [ -n "$_wlan" ]; then + printf '[' + _first=1 + for _dev in $_wlan; do + if [ "$_first" -ne 1 ]; then printf ','; fi + printf '"%s"' "$(json_escape "$_dev")" + _first=0 + done + printf ']' + else + printf '[]' + fi +} + +collect_usb_devices() { + if command -v usbconfig >/dev/null 2>&1; then + printf '%d' "$(usbconfig 2>/dev/null | grep -c '^ugen' || echo 0)" + else + printf '0' + fi +} + +collect_xorg_running() { + if pgrep -x Xorg >/dev/null 2>&1; then + printf 'true' + else + printf 'false' + fi +} + +collect_vulkan_support() { + if command -v vulkaninfo >/dev/null 2>&1 && \ + vulkaninfo --summary >/dev/null 2>&1; then + printf 'true' + else + printf 'false' + fi +} + +collect_boot_type() { + if [ -e /dev/da0 ]; then + printf '"usb"' + elif [ -e /dev/nda0 ] || [ -e /dev/nvd0 ]; then + printf '"nvme"' + elif [ -e /dev/ada0 ]; then + printf '"sata"' + else + printf '"unknown"' + fi +} + +collect_colibri_running() { + if command -v service >/dev/null 2>&1; then + # service status can hang on rc.d scripts; enforce a 1s deadline + _status="$(timeout 1 service colibri_daemon status 2>/dev/null || echo "not_running")" + case "$_status" in + *"is running"*|*"Running"*) + printf 'true' + ;; + *) + printf 'false' + ;; + esac + else + printf 'false' + fi +} + +collect_zfs_pools() { + if command -v zpool >/dev/null 2>&1; then + _pools="$(zpool list -H -o name 2>/dev/null || true)" + if [ -n "$_pools" ]; then + printf '[' + _first=1 + for _pool in $_pools; do + if [ "$_first" -ne 1 ]; then printf ','; fi + printf '"%s"' "$(json_escape "$_pool")" + _first=0 + done + printf ']' + return + fi + fi + printf '[]' +} + +######################################################################## +# assemble JSON +######################################################################## + +_hostname="$(collect_hostname)" +_freebsd_ver="$(collect_freebsd_version)" +_ram_gb="$(collect_ram_gb)" +_cpu_model="$(collect_cpu_model)" +_cpu_cores="$(collect_cpu_cores)" +_gpu="$(collect_gpu)" +_gpu_driver="$(collect_gpu_driver)" +_disks="$(collect_disks)" +_net="$(collect_network_interfaces)" +_wifi="$(collect_wifi)" +_usb="$(collect_usb_devices)" +_xorg="$(collect_xorg_running)" +_vulkan="$(collect_vulkan_support)" +_boot_type="$(collect_boot_type)" +_colibri="$(collect_colibri_running)" +_zfs_pools="$(collect_zfs_pools)" + +printf '{\n' +printf ' "schema_version":"clawdie.hw-probe.v1",\n' +printf ' "hostname":"%s",\n' "$(json_escape "$_hostname")" +printf ' "freebsd_version":%s,\n' "$_freebsd_ver" +printf ' "ram_gb":%s,\n' "$_ram_gb" +printf ' "cpu_model":"%s",\n' "$(json_escape "$_cpu_model")" +printf ' "cpu_cores":%s,\n' "$_cpu_cores" +printf ' "gpu":%s,\n' "$_gpu" +printf ' "gpu_driver":%s,\n' "$_gpu_driver" +printf ' "disks":%s,\n' "$_disks" +printf ' "network_interfaces":%s,\n' "$_net" +printf ' "wifi":%s,\n' "$_wifi" +printf ' "usb_devices":%s,\n' "$_usb" +printf ' "xorg_running":%s,\n' "$_xorg" +printf ' "vulkan_support":%s,\n' "$_vulkan" +printf ' "boot_type":%s,\n' "$_boot_type" +printf ' "colibri_running":%s,\n' "$_colibri" +printf ' "zfs_pools":%s\n' "$_zfs_pools" +printf '}\n' + +exit 0 diff --git a/live/operator-session/clawdie-live-seed.README.txt b/live/operator-session/clawdie-live-seed.README.txt index 045816f..97ae5b6 100644 --- a/live/operator-session/clawdie-live-seed.README.txt +++ b/live/operator-session/clawdie-live-seed.README.txt @@ -67,7 +67,7 @@ Inside it, any of these are honored: //harness.toml Which agent harness to run + basic knobs: harness = "zot" # zot | pi | local - model = "claude-opus-4-8" + model = "deepseek-v4-pro" cost_mode = "smart" `harness` must be one of zot, pi, local diff --git a/live/operator-session/hw-report.desktop b/live/operator-session/hw-report.desktop deleted file mode 100644 index 0e42d5f..0000000 --- a/live/operator-session/hw-report.desktop +++ /dev/null @@ -1,8 +0,0 @@ -[Desktop Entry] -Type=Application -Name=Clawdie Hardware Report -Comment=Collect local hardware diagnostics for support and custom ISO builds -Exec=xfce4-terminal --hold --command "mdo -u root /usr/local/bin/hw-report" -Icon=utilities-system-monitor -Terminal=false -Categories=System;Utility; diff --git a/packaging/mother/build-colibri.sh b/packaging/mother/build-colibri.sh new file mode 100755 index 0000000..36097f6 --- /dev/null +++ b/packaging/mother/build-colibri.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# build-colibri MCP tool — build a colibri crate from any git branch. +# +# Accepts a JSON-RPC tools/call request on stdin, builds the requested +# crate, and returns the result as a JSON content block to stdout. +# +# Expected input (MCP tools/call): +# {"jsonrpc":"2.0","method":"tools/call","id":1,"params":{"name":"build_colibri","arguments":{"crate":"colibri-daemon","branch":"main","features":"","release":"true"}}} +# +# Parameters: +# crate — workspace member to build (default: colibri-daemon) +# branch — git branch/ref to check out (default: main) +# features — optional comma-separated features +# release — "true" for --release, anything else for debug (default: true) +# +# Output: MCP JSON-RPC response with text content block +# {"success":true,"binary":"/home/clawdie/ai/colibri/target/release/colibri-daemon","duration_s":25.4,"branch":"main","commit":"abc1234"} +# or +# {"success":false,"error":"build failed: ..."} +set -eu + +REPO="/home/clawdie/ai/colibri" +CRATE_DEFAULT="colibri-daemon" +BRANCH_DEFAULT="main" + +# Read JSON from stdin, extract arguments +INPUT=$(cat) +CRATE=$(echo "$INPUT" | jq -r '.params.arguments.crate // "'"$CRATE_DEFAULT"'"') +BRANCH=$(echo "$INPUT" | jq -r '.params.arguments.branch // "'"$BRANCH_DEFAULT"'"') +FEATURES=$(echo "$INPUT" | jq -r '.params.arguments.features // ""') +RELEASE=$(echo "$INPUT" | jq -r '.params.arguments.release // "true"') +ID=$(echo "$INPUT" | jq -r '.id // "1"') + +# Build flags +CARGO_FLAGS="" +if [ "$RELEASE" = "true" ]; then + CARGO_FLAGS="--release" +fi +if [ -n "$FEATURES" ]; then + CARGO_FLAGS="$CARGO_FLAGS --features $FEATURES" +fi + +START_TS=$(date +%s) + +# Fetch and checkout +cd "$REPO" +git fetch origin 2>&1 || true +if ! git checkout "$BRANCH" 2>&1; then + printf '{"jsonrpc":"2.0","id":%s,"error":{"code":-1,"message":"git checkout failed: %s"}}\n' "$ID" "$BRANCH" + exit 1 +fi +COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") + +# Build +BUILD_LOG=$(mktemp) +if cargo build $CARGO_FLAGS -p "$CRATE" >"$BUILD_LOG" 2>&1; then + DURATION=$(( $(date +%s) - START_TS )) + BINARY=$(find target -maxdepth 3 -type f -name "$CRATE" -perm -111 | grep -v deps | head -1) + SIZE=$(stat -f '%z' "$BINARY" 2>/dev/null || echo "0") + cat "$BUILD_LOG" | tail -5 >&2 # send tail to stderr for logging + printf '{"jsonrpc":"2.0","id":%s,"result":{"content":[{"type":"text","text":"{\\"success\\":true,\\"binary\\":\\"%s\\",\\"duration_s\\":%s,\\"branch\\":\\"%s\\",\\"commit\\":\\"%s\\",\\"size_bytes\\":%s}"}]}}\n' \ + "$ID" "$BINARY" "$DURATION" "$BRANCH" "$COMMIT" "$SIZE" +else + DURATION=$(( $(date +%s) - START_TS )) + ERROR=$(tail -20 "$BUILD_LOG" | head -10 | tr '\n' ' ' | sed 's/"/\\"/g') + printf '{"jsonrpc":"2.0","id":%s,"error":{"code":-1,"message":"build failed in %ss: %s"}}\n' "$ID" "$DURATION" "$ERROR" +fi +rm -f "$BUILD_LOG" diff --git a/packaging/mother/colibri-mcp-ssh b/packaging/mother/colibri-mcp-ssh new file mode 100755 index 0000000..5a96f86 --- /dev/null +++ b/packaging/mother/colibri-mcp-ssh @@ -0,0 +1,6 @@ +#!/bin/sh +# Wrapper for SSH forced-command MCP entrypoint. +# SSH's command="..." restriction replaces the client's command with +# this script and stores the original in $SSH_ORIGINAL_COMMAND. +# Pass it through to colibri-mcp so `ssh colibri@mother tools` works. +exec /usr/local/bin/colibri-mcp ${SSH_ORIGINAL_COMMAND} diff --git a/scripts/stage-colibri-iso.sh b/scripts/stage-colibri-iso.sh index 6f4cb71..342e175 100755 --- a/scripts/stage-colibri-iso.sh +++ b/scripts/stage-colibri-iso.sh @@ -153,7 +153,7 @@ BW_SERVER="https://vault.smilepowered.org" # # Optional endpoints/models: # DEEPSEEK_ENDPOINT="https://api.deepseek.com/chat/completions" -# DEEPSEEK_MODEL="deepseek-chat" +# DEEPSEEK_MODEL="deepseek-v4-pro" # # Behavior toggles (non-secret): # COLIBRI_AUTOSPAWN_PI="YES" # auto-spawn one Pi on daemon startup