Dmitry Beresnev commited on
Commit
15c8440
·
1 Parent(s): 0c86899

add more openclaw alternative services

Browse files
Files changed (9) hide show
  1. Caddyfile +21 -0
  2. Dockerfile +45 -0
  3. app.py +455 -16
  4. nanobot.json +1 -0
  5. nanoclaw.json +1 -0
  6. nullclaw.json +1 -0
  7. picoclaw.json +1 -0
  8. supervisord.conf +66 -0
  9. zeroclaw.json +1 -0
Caddyfile CHANGED
@@ -8,6 +8,27 @@ handle /openclaw* {
8
  }
9
  }
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  # OpenClaw UI uses absolute runtime paths; proxy them too.
12
  handle /ws* {
13
  reverse_proxy 127.0.0.1:18789 {
 
8
  }
9
  }
10
 
11
+ # NanoClaw UI mounted at /nanoclaw (preserve prefix).
12
+ handle /nanoclaw* {
13
+ reverse_proxy 127.0.0.1:{$NANOCLAW_PORT}
14
+ }
15
+
16
+ handle /nanobot* {
17
+ reverse_proxy 127.0.0.1:{$NANOBOT_PORT}
18
+ }
19
+
20
+ handle /picoclaw* {
21
+ reverse_proxy 127.0.0.1:{$PICOCLAW_PORT}
22
+ }
23
+
24
+ handle /zeroclaw* {
25
+ reverse_proxy 127.0.0.1:{$ZEROCLAW_PORT}
26
+ }
27
+
28
+ handle /nullclaw* {
29
+ reverse_proxy 127.0.0.1:{$NULLCLAW_PORT}
30
+ }
31
+
32
  # OpenClaw UI uses absolute runtime paths; proxy them too.
33
  handle /ws* {
34
  reverse_proxy 127.0.0.1:18789 {
Dockerfile CHANGED
@@ -58,6 +58,51 @@ ENV STREAMLIT_AUTH_ENABLED=1
58
  ENV STREAMLIT_AUTH_USERNAME=
59
  ENV STREAMLIT_AUTH_PASSWORD=
60
  ENV LLAMA_SERVER_CTX_SIZE=8192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
  RUN mkdir -p /app/vault /app/.openclaw/state
63
 
 
58
  ENV STREAMLIT_AUTH_USERNAME=
59
  ENV STREAMLIT_AUTH_PASSWORD=
60
  ENV LLAMA_SERVER_CTX_SIZE=8192
61
+ ENV NANOCLAW_ENABLED=1
62
+ ENV NANOCLAW_PORT=18889
63
+ ENV NANOCLAW_CMD=nanoclaw
64
+ ENV NANOCLAW_ARGS=
65
+ ENV NANOCLAW_BASE_PATH=/nanoclaw
66
+ ENV NANOCLAW_CONFIG_PATH=/app/nanoclaw.json
67
+ ENV NANOCLAW_LOG_PATH=/tmp/nanoclaw.log
68
+ ENV NANOCLAW_ERR_LOG_PATH=/tmp/nanoclaw.err.log
69
+ ENV NANOBOT_ENABLED=1
70
+ ENV NANOBOT_PORT=18790
71
+ ENV NANOBOT_CMD=nanobot
72
+ ENV NANOBOT_ARGS=gateway --port 18790
73
+ ENV NANOBOT_BASE_PATH=/nanobot
74
+ ENV NANOBOT_CONFIG_PATH=/app/nanobot.json
75
+ ENV NANOBOT_LOG_PATH=/tmp/nanobot.log
76
+ ENV NANOBOT_ERR_LOG_PATH=/tmp/nanobot.err.log
77
+ ENV PICOCLAW_ENABLED=1
78
+ ENV PICOCLAW_PORT=18792
79
+ ENV PICOCLAW_CMD=picoclaw
80
+ ENV PICOCLAW_ARGS=gateway --port 18792
81
+ ENV PICOCLAW_BASE_PATH=/picoclaw
82
+ ENV PICOCLAW_CONFIG_PATH=/app/picoclaw.json
83
+ ENV PICOCLAW_LOG_PATH=/tmp/picoclaw.log
84
+ ENV PICOCLAW_ERR_LOG_PATH=/tmp/picoclaw.err.log
85
+ ENV IRONCLAW_ENABLED=1
86
+ ENV IRONCLAW_CMD=ironclaw
87
+ ENV IRONCLAW_ARGS=
88
+ ENV IRONCLAW_LOG_PATH=/tmp/ironclaw.log
89
+ ENV IRONCLAW_ERR_LOG_PATH=/tmp/ironclaw.err.log
90
+ ENV ZEROCLAW_ENABLED=1
91
+ ENV ZEROCLAW_PORT=42617
92
+ ENV ZEROCLAW_CMD=zeroclaw
93
+ ENV ZEROCLAW_ARGS=gateway --port 42617
94
+ ENV ZEROCLAW_BASE_PATH=/zeroclaw
95
+ ENV ZEROCLAW_CONFIG_PATH=/app/zeroclaw.json
96
+ ENV ZEROCLAW_LOG_PATH=/tmp/zeroclaw.log
97
+ ENV ZEROCLAW_ERR_LOG_PATH=/tmp/zeroclaw.err.log
98
+ ENV NULLCLAW_ENABLED=1
99
+ ENV NULLCLAW_PORT=3000
100
+ ENV NULLCLAW_CMD=nullclaw
101
+ ENV NULLCLAW_ARGS=gateway --port 3000
102
+ ENV NULLCLAW_BASE_PATH=/nullclaw
103
+ ENV NULLCLAW_CONFIG_PATH=/app/nullclaw.json
104
+ ENV NULLCLAW_LOG_PATH=/tmp/nullclaw.log
105
+ ENV NULLCLAW_ERR_LOG_PATH=/tmp/nullclaw.err.log
106
 
107
  RUN mkdir -p /app/vault /app/.openclaw/state
108
 
app.py CHANGED
@@ -25,6 +25,11 @@ OPENCLAW_PORT = int(os.getenv("OPENCLAW_PORT", "18789"))
25
  VAULT_PATH = os.getenv("VAULT_PATH", "/app/vault")
26
  OPENCLAW_BIN_ENV = os.getenv("OPENCLAW_BIN", "openclaw")
27
  CONFIG_PATH = Path(os.getenv("OPENCLAW_CONFIG_PATH", "openclaw.json"))
 
 
 
 
 
28
  ENV_EXAMPLE_PATH = Path("config/openclaw.env.example")
29
  LOG_MAX_LINES = 300
30
  OPENCLAW_STANDARD_UI_URL = os.getenv(
@@ -47,6 +52,49 @@ STREAMLIT_ERR_LOG_PATH = Path("/tmp/streamlit.err.log")
47
  STREAMLIT_LOG_PATH = Path("/tmp/streamlit.log")
48
  CADDY_ERR_LOG_PATH = Path("/tmp/caddy.err.log")
49
  CADDY_LOG_PATH = Path("/tmp/caddy.log")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  STREAMLIT_AUTH_ENABLED = os.getenv("STREAMLIT_AUTH_ENABLED", "1") == "1"
51
  STREAMLIT_AUTH_USERNAME = os.getenv("STREAMLIT_AUTH_USERNAME", "").strip()
52
  STREAMLIT_AUTH_PASSWORD = os.getenv("STREAMLIT_AUTH_PASSWORD", "").strip()
@@ -66,6 +114,11 @@ def init_state() -> None:
66
  st.session_state.setdefault("gateway_process", None)
67
  st.session_state.setdefault("gateway_logs", [])
68
  st.session_state.setdefault("config_editor_text", load_config_text())
 
 
 
 
 
69
  st.session_state.setdefault("auto_started", False)
70
  st.session_state.setdefault("auto_start_attempted", False)
71
  st.session_state.setdefault("backtest_result", None)
@@ -89,6 +142,71 @@ def load_config_json() -> dict:
89
  return {}
90
 
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  def gateway_process() -> subprocess.Popen | None:
93
  proc = st.session_state.get("gateway_process")
94
  if proc is None:
@@ -296,6 +414,53 @@ def is_gateway_port_open(timeout: float = 0.5) -> bool:
296
  return sock.connect_ex(("127.0.0.1", OPENCLAW_PORT)) == 0
297
 
298
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  def infer_llm_api_base() -> str:
300
  candidates = [
301
  os.getenv("LLM_SPACE_OPENAI_URL", "").strip(),
@@ -635,6 +800,62 @@ with action_col:
635
 
636
  st.divider()
637
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
  st.subheader("OpenClaw Standard UI")
639
  std_ui_status_col, std_ui_embed_col = st.columns([1, 3])
640
  with std_ui_status_col:
@@ -689,6 +910,115 @@ with std_ui_embed_col:
689
 
690
  st.divider()
691
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
  cfg_col, env_col = st.columns(2)
693
 
694
  with cfg_col:
@@ -708,29 +1038,114 @@ with cfg_col:
708
  st.error(f"Invalid JSON: {exc}")
709
 
710
  with env_col:
711
- st.subheader("Environment Checks")
712
- config_data = load_config_json()
713
- expected_vars = parse_expected_env_vars(config_data)
714
- if not expected_vars:
715
- st.info("No expected env vars detected.")
716
- else:
717
- checks = []
718
- for key in expected_vars:
719
- is_set = bool(os.getenv(key, ""))
720
- checks.append(
721
- {
722
- "name": key,
723
- "status": "available" if is_set else "missing",
724
- "color": "green" if is_set else "red",
725
- }
726
  )
727
- for item in checks:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
728
  st.markdown(
729
  f"<span style='color:{item['color']}'>&#9679;</span> "
730
  f"<b>{item['name']}</b> - {item['status']}",
731
  unsafe_allow_html=True,
732
  )
733
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
734
  st.divider()
735
 
736
  test_col, logs_col = st.columns([2, 3])
@@ -753,6 +1168,30 @@ st.expander("Service Diagnostics (Supervisor/Caddy/Streamlit/OpenClaw)").code(
753
  tail_text_file(GATEWAY_ERR_LOG_PATH),
754
  "=== openclaw-gateway.log ===",
755
  tail_text_file(GATEWAY_BOOT_LOG_PATH),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
756
  "=== caddy.err.log ===",
757
  tail_text_file(CADDY_ERR_LOG_PATH),
758
  "=== caddy.log ===",
 
25
  VAULT_PATH = os.getenv("VAULT_PATH", "/app/vault")
26
  OPENCLAW_BIN_ENV = os.getenv("OPENCLAW_BIN", "openclaw")
27
  CONFIG_PATH = Path(os.getenv("OPENCLAW_CONFIG_PATH", "openclaw.json"))
28
+ NANOCLAW_CONFIG_PATH = Path(os.getenv("NANOCLAW_CONFIG_PATH", "nanoclaw.json"))
29
+ NANOBOT_CONFIG_PATH = Path(os.getenv("NANOBOT_CONFIG_PATH", "nanobot.json"))
30
+ PICOCLAW_CONFIG_PATH = Path(os.getenv("PICOCLAW_CONFIG_PATH", "picoclaw.json"))
31
+ ZEROCLAW_CONFIG_PATH = Path(os.getenv("ZEROCLAW_CONFIG_PATH", "zeroclaw.json"))
32
+ NULLCLAW_CONFIG_PATH = Path(os.getenv("NULLCLAW_CONFIG_PATH", "nullclaw.json"))
33
  ENV_EXAMPLE_PATH = Path("config/openclaw.env.example")
34
  LOG_MAX_LINES = 300
35
  OPENCLAW_STANDARD_UI_URL = os.getenv(
 
52
  STREAMLIT_LOG_PATH = Path("/tmp/streamlit.log")
53
  CADDY_ERR_LOG_PATH = Path("/tmp/caddy.err.log")
54
  CADDY_LOG_PATH = Path("/tmp/caddy.log")
55
+ NANOCLAW_LOG_PATH = Path(os.getenv("NANOCLAW_LOG_PATH", "/tmp/nanoclaw.log"))
56
+ NANOCLAW_ERR_LOG_PATH = Path(os.getenv("NANOCLAW_ERR_LOG_PATH", "/tmp/nanoclaw.err.log"))
57
+ NANOCLAW_PORT = int(os.getenv("NANOCLAW_PORT", "18889"))
58
+ NANOCLAW_BASE_PATH = os.getenv("NANOCLAW_BASE_PATH", "/nanoclaw")
59
+ NANOCLAW_ENABLED = os.getenv("NANOCLAW_ENABLED", "1") == "1"
60
+ NANOCLAW_PROXY_LOCAL_URL = os.getenv(
61
+ "NANOCLAW_PROXY_LOCAL_URL", f"http://127.0.0.1:7860{NANOCLAW_BASE_PATH}/"
62
+ )
63
+ NANOBOT_LOG_PATH = Path(os.getenv("NANOBOT_LOG_PATH", "/tmp/nanobot.log"))
64
+ NANOBOT_ERR_LOG_PATH = Path(os.getenv("NANOBOT_ERR_LOG_PATH", "/tmp/nanobot.err.log"))
65
+ NANOBOT_PORT = int(os.getenv("NANOBOT_PORT", "18790"))
66
+ NANOBOT_BASE_PATH = os.getenv("NANOBOT_BASE_PATH", "/nanobot")
67
+ NANOBOT_ENABLED = os.getenv("NANOBOT_ENABLED", "1") == "1"
68
+ NANOBOT_PROXY_LOCAL_URL = os.getenv(
69
+ "NANOBOT_PROXY_LOCAL_URL", f"http://127.0.0.1:7860{NANOBOT_BASE_PATH}/"
70
+ )
71
+ PICOCLAW_LOG_PATH = Path(os.getenv("PICOCLAW_LOG_PATH", "/tmp/picoclaw.log"))
72
+ PICOCLAW_ERR_LOG_PATH = Path(os.getenv("PICOCLAW_ERR_LOG_PATH", "/tmp/picoclaw.err.log"))
73
+ PICOCLAW_PORT = int(os.getenv("PICOCLAW_PORT", "18792"))
74
+ PICOCLAW_BASE_PATH = os.getenv("PICOCLAW_BASE_PATH", "/picoclaw")
75
+ PICOCLAW_ENABLED = os.getenv("PICOCLAW_ENABLED", "1") == "1"
76
+ PICOCLAW_PROXY_LOCAL_URL = os.getenv(
77
+ "PICOCLAW_PROXY_LOCAL_URL", f"http://127.0.0.1:7860{PICOCLAW_BASE_PATH}/"
78
+ )
79
+ ZEROCLAW_LOG_PATH = Path(os.getenv("ZEROCLAW_LOG_PATH", "/tmp/zeroclaw.log"))
80
+ ZEROCLAW_ERR_LOG_PATH = Path(os.getenv("ZEROCLAW_ERR_LOG_PATH", "/tmp/zeroclaw.err.log"))
81
+ ZEROCLAW_PORT = int(os.getenv("ZEROCLAW_PORT", "42617"))
82
+ ZEROCLAW_BASE_PATH = os.getenv("ZEROCLAW_BASE_PATH", "/zeroclaw")
83
+ ZEROCLAW_ENABLED = os.getenv("ZEROCLAW_ENABLED", "1") == "1"
84
+ ZEROCLAW_PROXY_LOCAL_URL = os.getenv(
85
+ "ZEROCLAW_PROXY_LOCAL_URL", f"http://127.0.0.1:7860{ZEROCLAW_BASE_PATH}/"
86
+ )
87
+ NULLCLAW_LOG_PATH = Path(os.getenv("NULLCLAW_LOG_PATH", "/tmp/nullclaw.log"))
88
+ NULLCLAW_ERR_LOG_PATH = Path(os.getenv("NULLCLAW_ERR_LOG_PATH", "/tmp/nullclaw.err.log"))
89
+ NULLCLAW_PORT = int(os.getenv("NULLCLAW_PORT", "3000"))
90
+ NULLCLAW_BASE_PATH = os.getenv("NULLCLAW_BASE_PATH", "/nullclaw")
91
+ NULLCLAW_ENABLED = os.getenv("NULLCLAW_ENABLED", "1") == "1"
92
+ NULLCLAW_PROXY_LOCAL_URL = os.getenv(
93
+ "NULLCLAW_PROXY_LOCAL_URL", f"http://127.0.0.1:7860{NULLCLAW_BASE_PATH}/"
94
+ )
95
+ IRONCLAW_LOG_PATH = Path(os.getenv("IRONCLAW_LOG_PATH", "/tmp/ironclaw.log"))
96
+ IRONCLAW_ERR_LOG_PATH = Path(os.getenv("IRONCLAW_ERR_LOG_PATH", "/tmp/ironclaw.err.log"))
97
+ IRONCLAW_ENABLED = os.getenv("IRONCLAW_ENABLED", "1") == "1"
98
  STREAMLIT_AUTH_ENABLED = os.getenv("STREAMLIT_AUTH_ENABLED", "1") == "1"
99
  STREAMLIT_AUTH_USERNAME = os.getenv("STREAMLIT_AUTH_USERNAME", "").strip()
100
  STREAMLIT_AUTH_PASSWORD = os.getenv("STREAMLIT_AUTH_PASSWORD", "").strip()
 
114
  st.session_state.setdefault("gateway_process", None)
115
  st.session_state.setdefault("gateway_logs", [])
116
  st.session_state.setdefault("config_editor_text", load_config_text())
117
+ st.session_state.setdefault("nanoclaw_config_text", load_nanoclaw_config_text())
118
+ st.session_state.setdefault("nanobot_config_text", load_nanobot_config_text())
119
+ st.session_state.setdefault("picoclaw_config_text", load_picoclaw_config_text())
120
+ st.session_state.setdefault("zeroclaw_config_text", load_zeroclaw_config_text())
121
+ st.session_state.setdefault("nullclaw_config_text", load_nullclaw_config_text())
122
  st.session_state.setdefault("auto_started", False)
123
  st.session_state.setdefault("auto_start_attempted", False)
124
  st.session_state.setdefault("backtest_result", None)
 
142
  return {}
143
 
144
 
145
+ def load_nanoclaw_config_text() -> str:
146
+ if NANOCLAW_CONFIG_PATH.exists():
147
+ return NANOCLAW_CONFIG_PATH.read_text(encoding="utf-8")
148
+ return "{}"
149
+
150
+
151
+ def load_nanoclaw_config_json() -> dict:
152
+ try:
153
+ return json.loads(load_nanoclaw_config_text())
154
+ except json.JSONDecodeError:
155
+ return {}
156
+
157
+
158
+ def load_nanobot_config_text() -> str:
159
+ if NANOBOT_CONFIG_PATH.exists():
160
+ return NANOBOT_CONFIG_PATH.read_text(encoding="utf-8")
161
+ return "{}"
162
+
163
+
164
+ def load_nanobot_config_json() -> dict:
165
+ try:
166
+ return json.loads(load_nanobot_config_text())
167
+ except json.JSONDecodeError:
168
+ return {}
169
+
170
+
171
+ def load_picoclaw_config_text() -> str:
172
+ if PICOCLAW_CONFIG_PATH.exists():
173
+ return PICOCLAW_CONFIG_PATH.read_text(encoding="utf-8")
174
+ return "{}"
175
+
176
+
177
+ def load_picoclaw_config_json() -> dict:
178
+ try:
179
+ return json.loads(load_picoclaw_config_text())
180
+ except json.JSONDecodeError:
181
+ return {}
182
+
183
+
184
+ def load_zeroclaw_config_text() -> str:
185
+ if ZEROCLAW_CONFIG_PATH.exists():
186
+ return ZEROCLAW_CONFIG_PATH.read_text(encoding="utf-8")
187
+ return "{}"
188
+
189
+
190
+ def load_zeroclaw_config_json() -> dict:
191
+ try:
192
+ return json.loads(load_zeroclaw_config_text())
193
+ except json.JSONDecodeError:
194
+ return {}
195
+
196
+
197
+ def load_nullclaw_config_text() -> str:
198
+ if NULLCLAW_CONFIG_PATH.exists():
199
+ return NULLCLAW_CONFIG_PATH.read_text(encoding="utf-8")
200
+ return "{}"
201
+
202
+
203
+ def load_nullclaw_config_json() -> dict:
204
+ try:
205
+ return json.loads(load_nullclaw_config_text())
206
+ except json.JSONDecodeError:
207
+ return {}
208
+
209
+
210
  def gateway_process() -> subprocess.Popen | None:
211
  proc = st.session_state.get("gateway_process")
212
  if proc is None:
 
414
  return sock.connect_ex(("127.0.0.1", OPENCLAW_PORT)) == 0
415
 
416
 
417
+ def is_nanoclaw_port_open(timeout: float = 0.5) -> bool:
418
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
419
+ sock.settimeout(timeout)
420
+ return sock.connect_ex(("127.0.0.1", NANOCLAW_PORT)) == 0
421
+
422
+
423
+ def is_nanobot_port_open(timeout: float = 0.5) -> bool:
424
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
425
+ sock.settimeout(timeout)
426
+ return sock.connect_ex(("127.0.0.1", NANOBOT_PORT)) == 0
427
+
428
+
429
+ def is_picoclaw_port_open(timeout: float = 0.5) -> bool:
430
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
431
+ sock.settimeout(timeout)
432
+ return sock.connect_ex(("127.0.0.1", PICOCLAW_PORT)) == 0
433
+
434
+
435
+ def is_zeroclaw_port_open(timeout: float = 0.5) -> bool:
436
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
437
+ sock.settimeout(timeout)
438
+ return sock.connect_ex(("127.0.0.1", ZEROCLAW_PORT)) == 0
439
+
440
+
441
+ def is_nullclaw_port_open(timeout: float = 0.5) -> bool:
442
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
443
+ sock.settimeout(timeout)
444
+ return sock.connect_ex(("127.0.0.1", NULLCLAW_PORT)) == 0
445
+
446
+
447
+ def supervisorctl(action: str, program: str) -> tuple[bool, str]:
448
+ cmd = shutil.which("supervisorctl")
449
+ if not cmd:
450
+ return False, "supervisorctl not available in container."
451
+ try:
452
+ result = subprocess.run(
453
+ [cmd, action, program],
454
+ check=True,
455
+ capture_output=True,
456
+ text=True,
457
+ )
458
+ return True, result.stdout.strip() or result.stderr.strip()
459
+ except subprocess.CalledProcessError as exc:
460
+ msg = exc.stderr.strip() or exc.stdout.strip() or str(exc)
461
+ return False, msg
462
+
463
+
464
  def infer_llm_api_base() -> str:
465
  candidates = [
466
  os.getenv("LLM_SPACE_OPENAI_URL", "").strip(),
 
800
 
801
  st.divider()
802
 
803
+ st.subheader("Services")
804
+ svc_col_1, svc_col_2 = st.columns([2, 3])
805
+ with svc_col_1:
806
+ st.markdown("**Status**")
807
+ openclaw_up = is_gateway_port_open()
808
+ nanoclaw_up = is_nanoclaw_port_open() if NANOCLAW_ENABLED else False
809
+ nanobot_up = is_nanobot_port_open() if NANOBOT_ENABLED else False
810
+ picoclaw_up = is_picoclaw_port_open() if PICOCLAW_ENABLED else False
811
+ zeroclaw_up = is_zeroclaw_port_open() if ZEROCLAW_ENABLED else False
812
+ nullclaw_up = is_nullclaw_port_open() if NULLCLAW_ENABLED else False
813
+ st.write(f"OpenClaw: {'🟢 Running' if openclaw_up else '🔴 Down'} (port {OPENCLAW_PORT})")
814
+ st.write(f"NanoClaw: {'🟢 Running' if nanoclaw_up else '🔴 Down'} (port {NANOCLAW_PORT})")
815
+ st.write(f"NanoBot: {'🟢 Running' if nanobot_up else '🔴 Down'} (port {NANOBOT_PORT})")
816
+ st.write(f"PicoClaw: {'🟢 Running' if picoclaw_up else '🔴 Down'} (port {PICOCLAW_PORT})")
817
+ st.write(f"ZeroClaw: {'🟢 Running' if zeroclaw_up else '🔴 Down'} (port {ZEROCLAW_PORT})")
818
+ st.write(f"NullClaw: {'🟢 Running' if nullclaw_up else '🔴 Down'} (port {NULLCLAW_PORT})")
819
+ st.write(f"IronClaw: {'🟢 Enabled' if IRONCLAW_ENABLED else '🔴 Disabled'} (no gateway port)")
820
+
821
+ with svc_col_2:
822
+ st.markdown("**Controls**")
823
+ if st.button("Restart OpenClaw", use_container_width=True):
824
+ ok, msg = supervisorctl("restart", "openclaw")
825
+ (st.success if ok else st.error)(msg)
826
+ if st.button("Restart NanoClaw", use_container_width=True):
827
+ ok, msg = supervisorctl("restart", "nanoclaw")
828
+ (st.success if ok else st.error)(msg)
829
+ if st.button("Restart NanoBot", use_container_width=True):
830
+ ok, msg = supervisorctl("restart", "nanobot")
831
+ (st.success if ok else st.error)(msg)
832
+ if st.button("Restart PicoClaw", use_container_width=True):
833
+ ok, msg = supervisorctl("restart", "picoclaw")
834
+ (st.success if ok else st.error)(msg)
835
+ if st.button("Restart ZeroClaw", use_container_width=True):
836
+ ok, msg = supervisorctl("restart", "zeroclaw")
837
+ (st.success if ok else st.error)(msg)
838
+ if st.button("Restart NullClaw", use_container_width=True):
839
+ ok, msg = supervisorctl("restart", "nullclaw")
840
+ (st.success if ok else st.error)(msg)
841
+ if st.button("Restart IronClaw", use_container_width=True):
842
+ ok, msg = supervisorctl("restart", "ironclaw")
843
+ (st.success if ok else st.error)(msg)
844
+ if not NANOCLAW_ENABLED:
845
+ st.info("NanoClaw is disabled. Set `NANOCLAW_ENABLED=1` to start it.")
846
+ if not NANOBOT_ENABLED:
847
+ st.info("NanoBot is disabled. Set `NANOBOT_ENABLED=1` to start it.")
848
+ if not PICOCLAW_ENABLED:
849
+ st.info("PicoClaw is disabled. Set `PICOCLAW_ENABLED=1` to start it.")
850
+ if not ZEROCLAW_ENABLED:
851
+ st.info("ZeroClaw is disabled. Set `ZEROCLAW_ENABLED=1` to start it.")
852
+ if not NULLCLAW_ENABLED:
853
+ st.info("NullClaw is disabled. Set `NULLCLAW_ENABLED=1` to start it.")
854
+ if not IRONCLAW_ENABLED:
855
+ st.info("IronClaw is disabled. Set `IRONCLAW_ENABLED=1` to start it.")
856
+
857
+ st.divider()
858
+
859
  st.subheader("OpenClaw Standard UI")
860
  std_ui_status_col, std_ui_embed_col = st.columns([1, 3])
861
  with std_ui_status_col:
 
910
 
911
  st.divider()
912
 
913
+ st.subheader("NanoClaw UI")
914
+ nano_status_col, nano_link_col = st.columns([1, 3])
915
+ with nano_status_col:
916
+ nano_ok = False
917
+ nano_status = "Unavailable"
918
+ if NANOCLAW_ENABLED:
919
+ try:
920
+ nano_resp = requests.get(NANOCLAW_PROXY_LOCAL_URL, timeout=2, allow_redirects=True)
921
+ nano_ok = nano_resp.status_code < 500
922
+ nano_status = "Available" if nano_ok else f"HTTP {nano_resp.status_code}"
923
+ except Exception:
924
+ nano_ok = False
925
+ nano_status = "Unavailable"
926
+ st.markdown(
927
+ f"NanoClaw ({NANOCLAW_PROXY_LOCAL_URL}): "
928
+ f"{'🟢' if nano_ok else '🔴'} {nano_status}"
929
+ )
930
+ if not NANOCLAW_ENABLED:
931
+ st.caption("NanoClaw is disabled.")
932
+
933
+ with nano_link_col:
934
+ if NANOCLAW_ENABLED:
935
+ st.markdown(
936
+ f'<a href="{NANOCLAW_PROXY_LOCAL_URL}" target="_blank" rel="noopener noreferrer">Open NanoClaw in new tab</a>',
937
+ unsafe_allow_html=True,
938
+ )
939
+ st.caption("If embedded UI fails, use the new tab link.")
940
+ else:
941
+ st.info("Enable NanoClaw to access its UI.")
942
+
943
+ st.divider()
944
+
945
+ st.subheader("Claw UIs")
946
+ claw_ui_col_1, claw_ui_col_2 = st.columns(2)
947
+ with claw_ui_col_1:
948
+ st.markdown("**NanoBot**")
949
+ nanobot_ok = False
950
+ if NANOBOT_ENABLED:
951
+ try:
952
+ nanobot_resp = requests.get(NANOBOT_PROXY_LOCAL_URL, timeout=2, allow_redirects=True)
953
+ nanobot_ok = nanobot_resp.status_code < 500
954
+ except Exception:
955
+ nanobot_ok = False
956
+ if NANOBOT_ENABLED:
957
+ st.markdown(
958
+ f'<a href="{NANOBOT_PROXY_LOCAL_URL}" target="_blank" rel="noopener noreferrer">Open NanoBot</a>',
959
+ unsafe_allow_html=True,
960
+ )
961
+ st.caption(f"Proxy: {NANOBOT_PROXY_LOCAL_URL} ({'ok' if nanobot_ok else 'down'})")
962
+ else:
963
+ st.caption("Disabled")
964
+
965
+ st.markdown("**PicoClaw**")
966
+ picoclaw_ok = False
967
+ if PICOCLAW_ENABLED:
968
+ try:
969
+ picoclaw_resp = requests.get(PICOCLAW_PROXY_LOCAL_URL, timeout=2, allow_redirects=True)
970
+ picoclaw_ok = picoclaw_resp.status_code < 500
971
+ except Exception:
972
+ picoclaw_ok = False
973
+ if PICOCLAW_ENABLED:
974
+ st.markdown(
975
+ f'<a href="{PICOCLAW_PROXY_LOCAL_URL}" target="_blank" rel="noopener noreferrer">Open PicoClaw</a>',
976
+ unsafe_allow_html=True,
977
+ )
978
+ st.caption(f"Proxy: {PICOCLAW_PROXY_LOCAL_URL} ({'ok' if picoclaw_ok else 'down'})")
979
+ else:
980
+ st.caption("Disabled")
981
+
982
+ st.markdown("**ZeroClaw**")
983
+ zeroclaw_ok = False
984
+ if ZEROCLAW_ENABLED:
985
+ try:
986
+ zeroclaw_resp = requests.get(ZEROCLAW_PROXY_LOCAL_URL, timeout=2, allow_redirects=True)
987
+ zeroclaw_ok = zeroclaw_resp.status_code < 500
988
+ except Exception:
989
+ zeroclaw_ok = False
990
+ if ZEROCLAW_ENABLED:
991
+ st.markdown(
992
+ f'<a href="{ZEROCLAW_PROXY_LOCAL_URL}" target="_blank" rel="noopener noreferrer">Open ZeroClaw</a>',
993
+ unsafe_allow_html=True,
994
+ )
995
+ st.caption(f"Proxy: {ZEROCLAW_PROXY_LOCAL_URL} ({'ok' if zeroclaw_ok else 'down'})")
996
+ else:
997
+ st.caption("Disabled")
998
+
999
+ with claw_ui_col_2:
1000
+ st.markdown("**NullClaw**")
1001
+ nullclaw_ok = False
1002
+ if NULLCLAW_ENABLED:
1003
+ try:
1004
+ nullclaw_resp = requests.get(NULLCLAW_PROXY_LOCAL_URL, timeout=2, allow_redirects=True)
1005
+ nullclaw_ok = nullclaw_resp.status_code < 500
1006
+ except Exception:
1007
+ nullclaw_ok = False
1008
+ if NULLCLAW_ENABLED:
1009
+ st.markdown(
1010
+ f'<a href="{NULLCLAW_PROXY_LOCAL_URL}" target="_blank" rel="noopener noreferrer">Open NullClaw</a>',
1011
+ unsafe_allow_html=True,
1012
+ )
1013
+ st.caption(f"Proxy: {NULLCLAW_PROXY_LOCAL_URL} ({'ok' if nullclaw_ok else 'down'})")
1014
+ else:
1015
+ st.caption("Disabled")
1016
+
1017
+ st.markdown("**IronClaw**")
1018
+ st.caption("No gateway port detected. Run `ironclaw` CLI after onboarding.")
1019
+
1020
+ st.divider()
1021
+
1022
  cfg_col, env_col = st.columns(2)
1023
 
1024
  with cfg_col:
 
1038
  st.error(f"Invalid JSON: {exc}")
1039
 
1040
  with env_col:
1041
+ st.subheader("NanoClaw Config")
1042
+ nanoclaw_text = st.text_area(
1043
+ "nanoclaw.json",
1044
+ value=st.session_state.get("nanoclaw_config_text", load_nanoclaw_config_text()),
1045
+ height=260,
1046
+ )
1047
+ st.session_state["nanoclaw_config_text"] = nanoclaw_text
1048
+ if st.button("Save NanoClaw Config", use_container_width=True):
1049
+ try:
1050
+ parsed = json.loads(nanoclaw_text)
1051
+ NANOCLAW_CONFIG_PATH.write_text(
1052
+ json.dumps(parsed, indent=2) + "\n", encoding="utf-8"
 
 
 
1053
  )
1054
+ st.success(f"Saved {NANOCLAW_CONFIG_PATH}.")
1055
+ except json.JSONDecodeError as exc:
1056
+ st.error(f"Invalid JSON: {exc}")
1057
+
1058
+ st.subheader("Environment Checks")
1059
+ config_data = load_config_json()
1060
+ expected_vars = parse_expected_env_vars(config_data)
1061
+ if not expected_vars:
1062
+ st.info("No expected env vars detected.")
1063
+ else:
1064
+ checks = []
1065
+ for key in expected_vars:
1066
+ is_set = bool(os.getenv(key, ""))
1067
+ checks.append(
1068
+ {
1069
+ "name": key,
1070
+ "status": "available" if is_set else "missing",
1071
+ "color": "green" if is_set else "red",
1072
+ }
1073
+ )
1074
+ for item in checks:
1075
  st.markdown(
1076
  f"<span style='color:{item['color']}'>&#9679;</span> "
1077
  f"<b>{item['name']}</b> - {item['status']}",
1078
  unsafe_allow_html=True,
1079
  )
1080
 
1081
+ st.subheader("Claw Configs")
1082
+ cfg_a, cfg_b = st.columns(2)
1083
+ with cfg_a:
1084
+ nanobot_text = st.text_area(
1085
+ "nanobot.json",
1086
+ value=st.session_state.get("nanobot_config_text", load_nanobot_config_text()),
1087
+ height=220,
1088
+ )
1089
+ st.session_state["nanobot_config_text"] = nanobot_text
1090
+ if st.button("Save NanoBot Config", use_container_width=True):
1091
+ try:
1092
+ parsed = json.loads(nanobot_text)
1093
+ NANOBOT_CONFIG_PATH.write_text(
1094
+ json.dumps(parsed, indent=2) + "\n", encoding="utf-8"
1095
+ )
1096
+ st.success(f"Saved {NANOBOT_CONFIG_PATH}.")
1097
+ except json.JSONDecodeError as exc:
1098
+ st.error(f"Invalid JSON: {exc}")
1099
+
1100
+ picoclaw_text = st.text_area(
1101
+ "picoclaw.json",
1102
+ value=st.session_state.get("picoclaw_config_text", load_picoclaw_config_text()),
1103
+ height=220,
1104
+ )
1105
+ st.session_state["picoclaw_config_text"] = picoclaw_text
1106
+ if st.button("Save PicoClaw Config", use_container_width=True):
1107
+ try:
1108
+ parsed = json.loads(picoclaw_text)
1109
+ PICOCLAW_CONFIG_PATH.write_text(
1110
+ json.dumps(parsed, indent=2) + "\n", encoding="utf-8"
1111
+ )
1112
+ st.success(f"Saved {PICOCLAW_CONFIG_PATH}.")
1113
+ except json.JSONDecodeError as exc:
1114
+ st.error(f"Invalid JSON: {exc}")
1115
+
1116
+ with cfg_b:
1117
+ zeroclaw_text = st.text_area(
1118
+ "zeroclaw.json",
1119
+ value=st.session_state.get("zeroclaw_config_text", load_zeroclaw_config_text()),
1120
+ height=220,
1121
+ )
1122
+ st.session_state["zeroclaw_config_text"] = zeroclaw_text
1123
+ if st.button("Save ZeroClaw Config", use_container_width=True):
1124
+ try:
1125
+ parsed = json.loads(zeroclaw_text)
1126
+ ZEROCLAW_CONFIG_PATH.write_text(
1127
+ json.dumps(parsed, indent=2) + "\n", encoding="utf-8"
1128
+ )
1129
+ st.success(f"Saved {ZEROCLAW_CONFIG_PATH}.")
1130
+ except json.JSONDecodeError as exc:
1131
+ st.error(f"Invalid JSON: {exc}")
1132
+
1133
+ nullclaw_text = st.text_area(
1134
+ "nullclaw.json",
1135
+ value=st.session_state.get("nullclaw_config_text", load_nullclaw_config_text()),
1136
+ height=220,
1137
+ )
1138
+ st.session_state["nullclaw_config_text"] = nullclaw_text
1139
+ if st.button("Save NullClaw Config", use_container_width=True):
1140
+ try:
1141
+ parsed = json.loads(nullclaw_text)
1142
+ NULLCLAW_CONFIG_PATH.write_text(
1143
+ json.dumps(parsed, indent=2) + "\n", encoding="utf-8"
1144
+ )
1145
+ st.success(f"Saved {NULLCLAW_CONFIG_PATH}.")
1146
+ except json.JSONDecodeError as exc:
1147
+ st.error(f"Invalid JSON: {exc}")
1148
+
1149
  st.divider()
1150
 
1151
  test_col, logs_col = st.columns([2, 3])
 
1168
  tail_text_file(GATEWAY_ERR_LOG_PATH),
1169
  "=== openclaw-gateway.log ===",
1170
  tail_text_file(GATEWAY_BOOT_LOG_PATH),
1171
+ "=== nanoclaw.err.log ===",
1172
+ tail_text_file(NANOCLAW_ERR_LOG_PATH),
1173
+ "=== nanoclaw.log ===",
1174
+ tail_text_file(NANOCLAW_LOG_PATH),
1175
+ "=== nanobot.err.log ===",
1176
+ tail_text_file(NANOBOT_ERR_LOG_PATH),
1177
+ "=== nanobot.log ===",
1178
+ tail_text_file(NANOBOT_LOG_PATH),
1179
+ "=== picoclaw.err.log ===",
1180
+ tail_text_file(PICOCLAW_ERR_LOG_PATH),
1181
+ "=== picoclaw.log ===",
1182
+ tail_text_file(PICOCLAW_LOG_PATH),
1183
+ "=== zeroclaw.err.log ===",
1184
+ tail_text_file(ZEROCLAW_ERR_LOG_PATH),
1185
+ "=== zeroclaw.log ===",
1186
+ tail_text_file(ZEROCLAW_LOG_PATH),
1187
+ "=== nullclaw.err.log ===",
1188
+ tail_text_file(NULLCLAW_ERR_LOG_PATH),
1189
+ "=== nullclaw.log ===",
1190
+ tail_text_file(NULLCLAW_LOG_PATH),
1191
+ "=== ironclaw.err.log ===",
1192
+ tail_text_file(IRONCLAW_ERR_LOG_PATH),
1193
+ "=== ironclaw.log ===",
1194
+ tail_text_file(IRONCLAW_LOG_PATH),
1195
  "=== caddy.err.log ===",
1196
  tail_text_file(CADDY_ERR_LOG_PATH),
1197
  "=== caddy.log ===",
nanobot.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {}
nanoclaw.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {}
nullclaw.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {}
picoclaw.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {}
supervisord.conf CHANGED
@@ -34,3 +34,69 @@ stopasgroup=true
34
  killasgroup=true
35
  stdout_logfile=/tmp/caddy.log
36
  stderr_logfile=/tmp/caddy.err.log
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  killasgroup=true
35
  stdout_logfile=/tmp/caddy.log
36
  stderr_logfile=/tmp/caddy.err.log
37
+
38
+ [program:nanoclaw]
39
+ command=/bin/sh -lc "if [ \"${NANOCLAW_ENABLED:-1}\" = \"1\" ]; then ${NANOCLAW_CMD} ${NANOCLAW_ARGS}; else echo '[nanoclaw] disabled'; sleep infinity; fi"
40
+ autorestart=true
41
+ startsecs=5
42
+ startretries=10
43
+ stopasgroup=true
44
+ killasgroup=true
45
+ stdout_logfile=/tmp/nanoclaw.log
46
+ stderr_logfile=/tmp/nanoclaw.err.log
47
+ environment=HOME="/root",NANOCLAW_CONFIG_PATH="/app/nanoclaw.json",NANOCLAW_PORT="18889",NANOCLAW_BASE_PATH="/nanoclaw"
48
+
49
+ [program:nanobot]
50
+ command=/bin/sh -lc "if [ \"${NANOBOT_ENABLED:-1}\" = \"1\" ]; then ${NANOBOT_CMD} ${NANOBOT_ARGS}; else echo '[nanobot] disabled'; sleep infinity; fi"
51
+ autorestart=true
52
+ startsecs=5
53
+ startretries=10
54
+ stopasgroup=true
55
+ killasgroup=true
56
+ stdout_logfile=/tmp/nanobot.log
57
+ stderr_logfile=/tmp/nanobot.err.log
58
+ environment=HOME="/root",NANOBOT_CONFIG_PATH="/app/nanobot.json",NANOBOT_PORT="18790",NANOBOT_BASE_PATH="/nanobot"
59
+
60
+ [program:picoclaw]
61
+ command=/bin/sh -lc "if [ \"${PICOCLAW_ENABLED:-1}\" = \"1\" ]; then ${PICOCLAW_CMD} ${PICOCLAW_ARGS}; else echo '[picoclaw] disabled'; sleep infinity; fi"
62
+ autorestart=true
63
+ startsecs=5
64
+ startretries=10
65
+ stopasgroup=true
66
+ killasgroup=true
67
+ stdout_logfile=/tmp/picoclaw.log
68
+ stderr_logfile=/tmp/picoclaw.err.log
69
+ environment=HOME="/root",PICOCLAW_CONFIG_PATH="/app/picoclaw.json",PICOCLAW_PORT="18792",PICOCLAW_BASE_PATH="/picoclaw"
70
+
71
+ [program:ironclaw]
72
+ command=/bin/sh -lc "if [ \"${IRONCLAW_ENABLED:-1}\" = \"1\" ]; then ${IRONCLAW_CMD} ${IRONCLAW_ARGS}; else echo '[ironclaw] disabled'; sleep infinity; fi"
73
+ autorestart=true
74
+ startsecs=5
75
+ startretries=10
76
+ stopasgroup=true
77
+ killasgroup=true
78
+ stdout_logfile=/tmp/ironclaw.log
79
+ stderr_logfile=/tmp/ironclaw.err.log
80
+ environment=HOME="/root"
81
+
82
+ [program:zeroclaw]
83
+ command=/bin/sh -lc "if [ \"${ZEROCLAW_ENABLED:-1}\" = \"1\" ]; then ${ZEROCLAW_CMD} ${ZEROCLAW_ARGS}; else echo '[zeroclaw] disabled'; sleep infinity; fi"
84
+ autorestart=true
85
+ startsecs=5
86
+ startretries=10
87
+ stopasgroup=true
88
+ killasgroup=true
89
+ stdout_logfile=/tmp/zeroclaw.log
90
+ stderr_logfile=/tmp/zeroclaw.err.log
91
+ environment=HOME="/root",ZEROCLAW_CONFIG_PATH="/app/zeroclaw.json",ZEROCLAW_PORT="42617",ZEROCLAW_BASE_PATH="/zeroclaw"
92
+
93
+ [program:nullclaw]
94
+ command=/bin/sh -lc "if [ \"${NULLCLAW_ENABLED:-1}\" = \"1\" ]; then ${NULLCLAW_CMD} ${NULLCLAW_ARGS}; else echo '[nullclaw] disabled'; sleep infinity; fi"
95
+ autorestart=true
96
+ startsecs=5
97
+ startretries=10
98
+ stopasgroup=true
99
+ killasgroup=true
100
+ stdout_logfile=/tmp/nullclaw.log
101
+ stderr_logfile=/tmp/nullclaw.err.log
102
+ environment=HOME="/root",NULLCLAW_CONFIG_PATH="/app/nullclaw.json",NULLCLAW_PORT="3000",NULLCLAW_BASE_PATH="/nullclaw"
zeroclaw.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {}