add zeroclaw local stack follow scripts

This commit is contained in:
Clément SAILLANT
2026-02-21 01:36:27 +01:00
parent b72fa3d53b
commit dcd35339a0
6 changed files with 192 additions and 6 deletions
@@ -4,13 +4,13 @@ on:
push:
branches: [main]
paths:
- "tools/ai/zeroclaw_dual_*.sh"
- "tools/ai/zeroclaw_*.sh"
- "specs/zeroclaw_dual_hw_*.md"
- ".github/workflows/zeroclaw_dual_orchestrator.yml"
pull_request:
branches: [main]
paths:
- "tools/ai/zeroclaw_dual_*.sh"
- "tools/ai/zeroclaw_*.sh"
- "specs/zeroclaw_dual_hw_*.md"
- ".github/workflows/zeroclaw_dual_orchestrator.yml"
workflow_dispatch:
@@ -35,6 +35,9 @@ jobs:
run: |
shellcheck tools/ai/zeroclaw_dual_bootstrap.sh
shellcheck tools/ai/zeroclaw_dual_chat.sh
shellcheck tools/ai/zeroclaw_stack_up.sh
shellcheck tools/ai/zeroclaw_stack_down.sh
shellcheck tools/ai/zeroclaw_webhook_send.sh
- name: Verify spec files
run: |
@@ -66,6 +66,14 @@ This keeps `autonomy.workspace_only = true` effective on a per-repo boundary.
- message mode (`-m`) or interactive mode,
- provider auto-fallback (`copilot` -> `openai-codex` -> `openrouter`),
- token sourcing from `gh auth token` at runtime only when `copilot` is selected.
- `tools/ai/zeroclaw_stack_up.sh`
- starts local gateway and local follow server,
- generates local follow page at `http://127.0.0.1:8788/`,
- stores pair token in `artifacts/zeroclaw/pair_token.txt`.
- `tools/ai/zeroclaw_stack_down.sh`
- stops local gateway/follow processes.
- `tools/ai/zeroclaw_webhook_send.sh`
- sends one webhook message and appends raw request/response trace to `artifacts/zeroclaw/conversations.jsonl`.
### 4.3 Provider/cost strategy
+15 -4
View File
@@ -11,10 +11,11 @@ Usage:
$(basename "$0") <rtc|zacus|path> -m "message"
$(basename "$0") <rtc|zacus|path> --interactive
$(basename "$0") <rtc|zacus|path> --hardware
$(basename "$0") <rtc|zacus|path> --provider-check
USAGE
}
if [[ $# -lt 2 ]]; then
if [[ $# -lt 1 ]]; then
usage
exit 1
fi
@@ -53,7 +54,7 @@ resolve_provider() {
return 0
fi
if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
if [[ "${ZEROCLAW_SKIP_COPILOT_CHECK:-0}" != "1" ]] && command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
if gh api /user/copilot/billing --silent >/dev/null 2>&1; then
local gh_token
gh_token="$(gh auth token)"
@@ -63,7 +64,12 @@ resolve_provider() {
fi
fi
if "$ZEROCLAW_BIN" auth list 2>/dev/null | rg -q "openai-codex:"; then
if env -u ZEROCLAW_WORKSPACE "$ZEROCLAW_BIN" auth status 2>/dev/null | rg -q "openai-codex:"; then
echo "openai-codex"
return 0
fi
if env -u ZEROCLAW_WORKSPACE "$ZEROCLAW_BIN" auth list 2>/dev/null | rg -q "openai-codex:"; then
echo "openai-codex"
return 0
fi
@@ -83,10 +89,15 @@ ERR
return 1
}
export ZEROCLAW_WORKSPACE="$workspace"
provider="$(resolve_provider)"
echo "[zeroclaw-dual-chat] workspace=$workspace provider=$provider"
if [[ "${1:-}" == "--provider-check" ]]; then
exit 0
fi
export ZEROCLAW_WORKSPACE="$workspace"
if [[ "${1:-}" == "--interactive" ]]; then
exec "$ZEROCLAW_BIN" agent -p "$provider"
fi
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
ART_DIR="${ZEROCLAW_ART_DIR:-/Users/cils/Documents/Lelectron_rare/Kill_LIFE/artifacts/zeroclaw}"
GW_PID_FILE="$ART_DIR/gateway.pid"
FW_PID_FILE="$ART_DIR/follow.pid"
stop_pid_file() {
local pid_file="$1"
[[ -f "$pid_file" ]] || return 0
local pid
pid="$(cat "$pid_file" 2>/dev/null || true)"
if [[ -n "$pid" ]] && kill -0 "$pid" >/dev/null 2>&1; then
kill "$pid" >/dev/null 2>&1 || true
sleep 0.2
if kill -0 "$pid" >/dev/null 2>&1; then
kill -9 "$pid" >/dev/null 2>&1 || true
fi
fi
rm -f "$pid_file"
}
stop_pid_file "$GW_PID_FILE"
stop_pid_file "$FW_PID_FILE"
echo "ZeroClaw local stack stopped."
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="/Users/cils/Documents/Lelectron_rare/Kill_LIFE"
ART_DIR="${ZEROCLAW_ART_DIR:-$ROOT_DIR/artifacts/zeroclaw}"
ZEROCLAW_BIN="${ZEROCLAW_BIN:-$ROOT_DIR/zeroclaw/target/release/zeroclaw}"
GATEWAY_HOST="${ZEROCLAW_GATEWAY_HOST:-127.0.0.1}"
GATEWAY_PORT="${ZEROCLAW_GATEWAY_PORT:-3000}"
FOLLOW_PORT="${ZEROCLAW_FOLLOW_PORT:-8788}"
GW_PID_FILE="$ART_DIR/gateway.pid"
FW_PID_FILE="$ART_DIR/follow.pid"
GW_LOG="$ART_DIR/gateway.log"
FW_LOG="$ART_DIR/follow.log"
TOKEN_FILE="$ART_DIR/pair_token.txt"
CONVO_FILE="$ART_DIR/conversations.jsonl"
INDEX_FILE="$ART_DIR/index.html"
mkdir -p "$ART_DIR"
touch "$CONVO_FILE"
if [[ ! -x "$ZEROCLAW_BIN" ]]; then
if command -v zeroclaw >/dev/null 2>&1; then
ZEROCLAW_BIN="$(command -v zeroclaw)"
else
echo "[fail] zeroclaw binary not found." >&2
exit 1
fi
fi
is_running() {
local pid_file="$1"
[[ -f "$pid_file" ]] || return 1
local pid
pid="$(cat "$pid_file" 2>/dev/null || true)"
[[ -n "$pid" ]] || return 1
kill -0 "$pid" >/dev/null 2>&1
}
if is_running "$GW_PID_FILE"; then
echo "[info] gateway already running (pid $(cat "$GW_PID_FILE"))."
else
nohup "$ZEROCLAW_BIN" gateway --port "$GATEWAY_PORT" --host "$GATEWAY_HOST" >"$GW_LOG" 2>&1 &
echo "$!" >"$GW_PID_FILE"
fi
if is_running "$FW_PID_FILE"; then
echo "[info] follow server already running (pid $(cat "$FW_PID_FILE"))."
else
nohup python3 -m http.server "$FOLLOW_PORT" --bind 127.0.0.1 --directory "$ART_DIR" >"$FW_LOG" 2>&1 &
echo "$!" >"$FW_PID_FILE"
fi
for _ in $(seq 1 40); do
if curl -fsS "http://$GATEWAY_HOST:$GATEWAY_PORT/health" >/dev/null 2>&1; then
break
fi
sleep 0.25
done
PAIR_CODE="$(grep -Eo 'X-Pairing-Code: [0-9]{6}' "$GW_LOG" 2>/dev/null | tail -1 | awk '{print $2}' || true)"
if [[ -n "$PAIR_CODE" ]]; then
PAIR_JSON="$(curl -sS -X POST "http://$GATEWAY_HOST:$GATEWAY_PORT/pair" -H "X-Pairing-Code: $PAIR_CODE" || true)"
TOKEN="$(printf '%s' "$PAIR_JSON" | python3 -c 'import json,sys
raw=sys.stdin.read().strip()
try:
obj=json.loads(raw) if raw else {}
except Exception:
obj={}
print(obj.get("token",""), end="")')"
if [[ -n "$TOKEN" ]]; then
printf '%s\n' "$TOKEN" >"$TOKEN_FILE"
chmod 600 "$TOKEN_FILE"
fi
fi
cat >"$INDEX_FILE" <<EOF
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="refresh" content="5" />
<title>ZeroClaw Local Follow</title>
<style>body{font-family:ui-sans-serif,system-ui;margin:20px}code{background:#f4f4f4;padding:2px 4px}</style>
</head>
<body>
<h1>ZeroClaw Local Follow</h1>
<ul>
<li>Health: <a href="http://$GATEWAY_HOST:$GATEWAY_PORT/health">http://$GATEWAY_HOST:$GATEWAY_PORT/health</a></li>
<li>Metrics: <a href="http://$GATEWAY_HOST:$GATEWAY_PORT/metrics">http://$GATEWAY_HOST:$GATEWAY_PORT/metrics</a></li>
<li>Gateway log: <a href="/gateway.log">/gateway.log</a></li>
<li>Conversation log: <a href="/conversations.jsonl">/conversations.jsonl</a></li>
</ul>
<p>Follow URL: <code>http://127.0.0.1:$FOLLOW_PORT/</code></p>
</body>
</html>
EOF
echo "Gateway: http://$GATEWAY_HOST:$GATEWAY_PORT/health"
echo "Follow : http://127.0.0.1:$FOLLOW_PORT/"
echo "Logs : $GW_LOG"
echo "Token : $TOKEN_FILE"
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env bash
set -euo pipefail
ART_DIR="${ZEROCLAW_ART_DIR:-/Users/cils/Documents/Lelectron_rare/Kill_LIFE/artifacts/zeroclaw}"
HOST="${ZEROCLAW_GATEWAY_HOST:-127.0.0.1}"
PORT="${ZEROCLAW_GATEWAY_PORT:-3000}"
TOKEN_FILE="$ART_DIR/pair_token.txt"
CONVO_FILE="$ART_DIR/conversations.jsonl"
if [[ $# -lt 1 ]]; then
echo "Usage: $(basename "$0") \"message\"" >&2
exit 1
fi
MESSAGE="$1"
TOKEN="${ZEROCLAW_BEARER:-}"
if [[ -z "$TOKEN" && -f "$TOKEN_FILE" ]]; then
TOKEN="$(cat "$TOKEN_FILE")"
fi
if [[ -z "$TOKEN" ]]; then
echo "[fail] no bearer token found. Start stack first or set ZEROCLAW_BEARER." >&2
exit 2
fi
PAYLOAD="$(python3 -c 'import json,sys;print(json.dumps({"message":sys.argv[1]}))' "$MESSAGE")"
RESPONSE="$(curl -sS -X POST "http://$HOST:$PORT/webhook" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "$PAYLOAD")"
TS="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
LINE="$(python3 -c 'import json,sys
ts,msg,resp=sys.argv[1],sys.argv[2],sys.argv[3]
print(json.dumps({"ts":ts,"message":msg,"response_raw":resp},ensure_ascii=False))' "$TS" "$MESSAGE" "$RESPONSE")"
printf '%s\n' "$LINE" >>"$CONVO_FILE"
printf '%s\n' "$RESPONSE"