Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d8282a3
Add Golf Mode: AI caddie with GPS, scorecard tracking, and club recom…
Feb 27, 2026
b5fbdee
Fix meeting mode disconnect and audio-only session state reset
Feb 27, 2026
36fd7ac
Major reliability overhaul: crash fixes, Discord notes, translation m…
Mar 4, 2026
8afc9ac
auto: update gateway-proxy.js,ngrok-url.sh,samples/CameraAccess/Camer…
Mar 9, 2026
3f85e0f
auto: update samples/CameraAccess/CameraAccess/Settings/SettingsManag…
Mar 9, 2026
7f01190
auto: update samples/CameraAccess/CameraAccess/Settings/SettingsManag…
Mar 9, 2026
79e6ff7
auto: update samples/CameraAccess/CameraAccess/Settings/SettingsView.…
Mar 9, 2026
b0dd470
auto: update samples/CameraAccess/CameraAccess/Settings/SettingsView.…
Mar 9, 2026
71878d4
auto: update samples/CameraAccess/CameraAccess/Settings/SettingsView.…
Mar 9, 2026
152c256
auto: update samples/CameraAccess/CameraAccess/Settings/SettingsView.…
Mar 9, 2026
141105f
auto: update samples/CameraAccess/CameraAccess/Gemini/ClubDistanceMod…
Mar 9, 2026
4c269b0
auto: update samples/CameraAccess/CameraAccess/Gemini/GeminiSessionVi…
Mar 9, 2026
3ee0e7c
auto: update samples/CameraAccess/CameraAccess/Gemini/GeminiSessionVi…
Mar 9, 2026
1640d64
auto: update samples/CameraAccess/CameraAccess/Gemini/GeminiSessionVi…
Mar 9, 2026
8116c49
auto: update samples/CameraAccess/CameraAccess/Gemini/GeminiSessionVi…
Mar 9, 2026
4f5ea5e
auto: update samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift
Mar 9, 2026
67e58a6
auto: update samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift
Mar 9, 2026
45da194
auto: update samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift
Mar 9, 2026
9b78e29
auto: update samples/CameraAccess/CameraAccess/Views/Components/GolfO…
Mar 9, 2026
470e3e3
auto: update samples/CameraAccess/CameraAccess/Gemini/GeminiSessionVi…
Mar 9, 2026
06b6067
auto: update samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj
Mar 9, 2026
ef4255a
auto: update samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj
Mar 9, 2026
ce824d4
auto: update samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj
Mar 9, 2026
91b5d86
auto: update samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj
Mar 9, 2026
c5e2ae1
auto: update samples/CameraAccess/CameraAccess/Settings/KeychainHelpe…
Mar 9, 2026
96a2cbe
auto: update samples/CameraAccess/CameraAccess/Settings/SettingsManag…
Mar 9, 2026
1d426cd
auto: update samples/CameraAccess/CameraAccess/Settings/SettingsView.…
Mar 9, 2026
8536d57
auto: update samples/CameraAccess/server/index.js
Mar 9, 2026
c9cc1ee
auto: update samples/CameraAccess/server/index.js
Mar 9, 2026
302fbfe
auto: update samples/CameraAccess/server/index.js
Mar 9, 2026
ad555e1
auto: update samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj
Mar 9, 2026
ec2d645
auto: update samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj
Mar 9, 2026
2ca2e38
auto: update samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj
Mar 9, 2026
d6613e0
auto: update samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj
Mar 9, 2026
ae7e044
auto: update samples/CameraAccess/CameraAccess.xcodeproj/project.pbxproj
Mar 9, 2026
2c09be2
auto: update samples/CameraAccess/CameraAccess/Gemini/GeminiConfig.swift
Mar 11, 2026
98608d6
auto: update rebuild-and-install.sh
Mar 22, 2026
0095418
auto: update rebuild-and-install.sh
Mar 22, 2026
67e4820
auto: update daily-healthcheck.sh
Mar 22, 2026
a450b24
auto: update daily-healthcheck.sh
Mar 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
102 changes: 102 additions & 0 deletions daily-healthcheck.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#!/usr/bin/env bash
# daily-healthcheck.sh — Daily health check for VisionClaw stack
# Verifies: proxy, signaling, ngrok, gateway, app cert age
# LaunchAgent: com.isdc.visionclaw-daily-healthcheck

set -euo pipefail

LOG="/opt/homebrew/var/log/visionclaw-healthcheck.log"
echo "=== VisionClaw Health Check $(date '+%Y-%m-%d %H:%M:%S') ===" >> "$LOG"

ISSUES=0

# 1. Gateway proxy (port 19000)
if curl -s -o /dev/null -w '' --max-time 5 http://localhost:19000/ 2>/dev/null; then
echo "[OK] Gateway proxy (19000)" >> "$LOG"
else
echo "[FAIL] Gateway proxy (19000) — not responding" >> "$LOG"
# Try to restart
launchctl kickstart -k "gui/$(id -u)/com.isdc.visionclaw-proxy" 2>/dev/null || true
echo " → Attempted restart" >> "$LOG"
ISSUES=$((ISSUES + 1))
fi

# 2. Signaling server (port 8080)
if curl -s -o /dev/null -w '' --max-time 5 http://localhost:8080/ 2>/dev/null; then
echo "[OK] Signaling server (8080)" >> "$LOG"
else
echo "[FAIL] Signaling server (8080) — not responding" >> "$LOG"
launchctl kickstart -k "gui/$(id -u)/com.isdc.visionclaw-signaling" 2>/dev/null || true
echo " → Attempted restart" >> "$LOG"
ISSUES=$((ISSUES + 1))
fi

# 3. OpenClaw gateway (port 18789)
if curl -s -o /dev/null -w '' --max-time 5 http://localhost:18789/ 2>/dev/null; then
echo "[OK] OpenClaw gateway (18789)" >> "$LOG"
else
echo "[FAIL] OpenClaw gateway (18789) — not responding" >> "$LOG"
ISSUES=$((ISSUES + 1))
fi

# 4. ngrok tunnel
TUNNEL_URL=$(curl -s --max-time 5 http://localhost:19000/api/tunnel-url 2>/dev/null | python3 -c "import json,sys; print(json.load(sys.stdin).get('tunnel_url',''))" 2>/dev/null || true)
if [[ -n "$TUNNEL_URL" ]]; then
TUNNEL_STATUS=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$TUNNEL_URL/" 2>/dev/null || echo "000")
if [[ "$TUNNEL_STATUS" == "200" ]]; then
echo "[OK] ngrok tunnel ($TUNNEL_URL)" >> "$LOG"
else
echo "[WARN] ngrok tunnel returned HTTP $TUNNEL_STATUS ($TUNNEL_URL)" >> "$LOG"
ISSUES=$((ISSUES + 1))
fi
else
echo "[FAIL] ngrok tunnel — no URL returned" >> "$LOG"
launchctl kickstart -k "gui/$(id -u)/com.isdc.ngrok-openclaw" 2>/dev/null || true
echo " → Attempted ngrok restart" >> "$LOG"
ISSUES=$((ISSUES + 1))
fi

# 5. TURN endpoint
if curl -s -o /dev/null -w '' --max-time 5 http://localhost:19000/api/turn 2>/dev/null; then
echo "[OK] TURN endpoint" >> "$LOG"
else
echo "[FAIL] TURN endpoint — not responding" >> "$LOG"
ISSUES=$((ISSUES + 1))
fi

# 6. Dev cert age check
APP_PATH="/Users/isdc/Library/Developer/Xcode/DerivedData/CameraAccess-dvtvpvflmpqsfbfeewfxnwrttfdr/Build/Products/Debug-iphoneos/CameraAccess.app"
if [[ -d "$APP_PATH" ]]; then
BUILD_EPOCH=$(stat -f %m "$APP_PATH" 2>/dev/null || echo "0")
NOW_EPOCH=$(date +%s)
DAYS_OLD=$(( (NOW_EPOCH - BUILD_EPOCH) / 86400 ))
if [[ "$DAYS_OLD" -ge 6 ]]; then
echo "[WARN] Dev cert expiring soon — build is ${DAYS_OLD} days old (expires at 7)" >> "$LOG"
ISSUES=$((ISSUES + 1))
else
echo "[OK] Dev cert — build is ${DAYS_OLD} days old" >> "$LOG"
fi
else
echo "[WARN] No build found at expected path" >> "$LOG"
fi

# 7. LaunchAgents status
for AGENT in com.isdc.visionclaw-proxy com.isdc.visionclaw-signaling com.isdc.ngrok-openclaw com.isdc.visionclaw-weekly-rebuild; do
if launchctl list | grep -q "$AGENT"; then
echo "[OK] LaunchAgent: $AGENT" >> "$LOG"
else
echo "[FAIL] LaunchAgent: $AGENT — not loaded" >> "$LOG"
launchctl load ~/Library/LaunchAgents/${AGENT}.plist 2>/dev/null || true
echo " → Attempted load" >> "$LOG"
ISSUES=$((ISSUES + 1))
fi
done

# Summary
echo "" >> "$LOG"
if [[ "$ISSUES" -eq 0 ]]; then
echo "[SUMMARY] All checks passed ✅" >> "$LOG"
else
echo "[SUMMARY] ${ISSUES} issue(s) found ⚠️" >> "$LOG"
fi
echo "" >> "$LOG"
119 changes: 119 additions & 0 deletions gateway-proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env node
// Unified Gateway Proxy for VisionClaw
// Routes through a single ngrok tunnel:
// - WebSocket connections → Signaling server (port 8080)
// - HTTP requests → OpenClaw gateway (port 18789)
//
// This allows one ngrok URL to handle everything.

const http = require("http");
const net = require("net");

const PROXY_PORT = parseInt(process.env.PROXY_PORT || "19000");
const OPENCLAW_PORT = parseInt(process.env.OPENCLAW_PORT || "18789");
const SIGNALING_PORT = parseInt(process.env.SIGNALING_PORT || "8080");
const OPENCLAW_HOST = process.env.OPENCLAW_HOST || "127.0.0.1";
const SIGNALING_HOST = process.env.SIGNALING_HOST || "127.0.0.1";

// Paths that should go to the signaling server (HTTP, not WS)
const SIGNALING_PATHS = ["/api/turn"];

// Cache ngrok URL (refreshed every 60s)
let cachedNgrokURL = null;
let lastNgrokCheck = 0;

async function getNgrokURL() {
const now = Date.now();
if (cachedNgrokURL && now - lastNgrokCheck < 60000) return cachedNgrokURL;
try {
const res = await fetch("http://localhost:4040/api/tunnels");
const data = await res.json();
const tunnel = data.tunnels?.[0];
if (tunnel) {
cachedNgrokURL = tunnel.public_url;
lastNgrokCheck = now;
}
} catch {}
return cachedNgrokURL;
}

// HTTP requests → route based on path
const server = http.createServer(async (req, res) => {
// Auto-discovery endpoint: returns current ngrok tunnel URL
// iOS app calls this over LAN to learn the tunnel URL for 5G use
if (req.url === "/api/tunnel-url") {
const url = await getNgrokURL();
res.writeHead(200, {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
});
res.end(JSON.stringify({ tunnel_url: url || null }));
return;
}

const isSignalingPath = SIGNALING_PATHS.some((p) => req.url.startsWith(p));
const targetHost = isSignalingPath ? SIGNALING_HOST : OPENCLAW_HOST;
const targetPort = isSignalingPath ? SIGNALING_PORT : OPENCLAW_PORT;
const targetLabel = isSignalingPath ? "Signaling" : "OpenClaw";

const proxyReq = http.request(
{
hostname: targetHost,
port: targetPort,
path: req.url,
method: req.method,
headers: req.headers,
},
(proxyRes) => {
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res);
}
);

proxyReq.on("error", (err) => {
console.error(`[Proxy] ${targetLabel} error: ${err.message}`);
res.writeHead(502, { "Content-Type": "text/plain" });
res.end(`${targetLabel} unreachable`);
});

req.pipe(proxyReq);
});

// WebSocket upgrade → Signaling server
server.on("upgrade", (req, socket, head) => {
console.log(`[Proxy] WebSocket upgrade → signaling (${req.url})`);

const proxySocket = net.connect(SIGNALING_PORT, SIGNALING_HOST, () => {
// Reconstruct the HTTP upgrade request to forward to signaling server
const headers = [`${req.method} ${req.url} HTTP/1.1`];
for (let i = 0; i < req.rawHeaders.length; i += 2) {
headers.push(`${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}`);
}
headers.push("", "");

proxySocket.write(headers.join("\r\n"));
if (head && head.length) {
proxySocket.write(head);
}

// Bidirectional pipe
proxySocket.pipe(socket);
socket.pipe(proxySocket);
});

proxySocket.on("error", (err) => {
console.error(`[Proxy] Signaling error: ${err.message}`);
socket.end();
});

socket.on("error", (err) => {
console.error(`[Proxy] Client socket error: ${err.message}`);
proxySocket.end();
});
});

server.listen(PROXY_PORT, "0.0.0.0", () => {
console.log(`[Proxy] Unified gateway running on port ${PROXY_PORT}`);
console.log(`[Proxy] HTTP → OpenClaw (${OPENCLAW_HOST}:${OPENCLAW_PORT})`);
console.log(`[Proxy] WS → Signaling (${SIGNALING_HOST}:${SIGNALING_PORT})`);
});
9 changes: 9 additions & 0 deletions ngrok-url.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash
# Gets the current ngrok tunnel URL for OpenClaw gateway
URL=$(curl -s http://localhost:4040/api/tunnels 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); tunnels=d.get('tunnels',[]); [print(t['public_url']) for t in tunnels]" 2>/dev/null)
if [ -z "$URL" ]; then
echo "ngrok not running. Start it: launchctl load ~/Library/LaunchAgents/com.isdc.ngrok-openclaw.plist"
exit 1
fi
echo "Current ngrok URL: $URL"
echo "Set this in VisionClaw app > Settings > Tunnel URL"
64 changes: 64 additions & 0 deletions rebuild-and-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# rebuild-and-install.sh — Build and install VisionClaw to iPhone
# Runs weekly to refresh the free dev certificate (expires every 7 days)
# LaunchAgent: com.isdc.visionclaw-weekly-rebuild

set -euo pipefail

LOG="/opt/homebrew/var/log/visionclaw-rebuild.log"
echo "=== VisionClaw Rebuild $(date '+%Y-%m-%d %H:%M:%S') ===" >> "$LOG"

# Step 1: Build
echo "[BUILD] Starting xcodebuild..." >> "$LOG"
cd /Users/isdc/VisionClaw/samples/CameraAccess
BUILD_OUTPUT=$(xcodebuild \
-project CameraAccess.xcodeproj \
-scheme CameraAccess \
-destination 'generic/platform=iOS' \
-allowProvisioningUpdates \
build 2>&1)

if echo "$BUILD_OUTPUT" | grep -q "BUILD SUCCEEDED"; then
echo "[BUILD] SUCCESS" >> "$LOG"
else
echo "[BUILD] FAILED" >> "$LOG"
echo "$BUILD_OUTPUT" | tail -20 >> "$LOG"
exit 1
fi

# Step 2: Find connected device
DEVICE_ID=$(xcrun devicectl list devices 2>/dev/null | grep "available" | head -1 | awk '{for(i=1;i<=NF;i++) if($i ~ /^[A-F0-9]{8}-/) print $i}')

if [[ -z "$DEVICE_ID" ]]; then
echo "[INSTALL] No device connected — skipping install" >> "$LOG"
exit 0
fi

echo "[INSTALL] Device: ${DEVICE_ID}" >> "$LOG"

# Step 3: Find the built .app
APP_PATH="/Users/isdc/Library/Developer/Xcode/DerivedData/CameraAccess-dvtvpvflmpqsfbfeewfxnwrttfdr/Build/Products/Debug-iphoneos/CameraAccess.app"

if [[ ! -d "$APP_PATH" ]]; then
echo "[INSTALL] App not found at expected path — searching..." >> "$LOG"
APP_PATH=$(find /Users/isdc/Library/Developer/Xcode/DerivedData -name "CameraAccess.app" -path "*/Debug-iphoneos/*" 2>/dev/null | head -1)
fi

if [[ -z "$APP_PATH" || ! -d "$APP_PATH" ]]; then
echo "[INSTALL] FAILED — could not find CameraAccess.app" >> "$LOG"
exit 1
fi

# Step 4: Install
INSTALL_OUTPUT=$(xcrun devicectl device install app --device "$DEVICE_ID" "$APP_PATH" 2>&1)

if echo "$INSTALL_OUTPUT" | grep -q "App installed"; then
echo "[INSTALL] SUCCESS — $(echo "$INSTALL_OUTPUT" | grep bundleID)" >> "$LOG"
else
echo "[INSTALL] FAILED" >> "$LOG"
echo "$INSTALL_OUTPUT" >> "$LOG"
exit 1
fi

echo "[DONE] VisionClaw rebuilt and installed at $(date '+%H:%M:%S')" >> "$LOG"
echo "" >> "$LOG"
Loading