somratpro Copilot commited on
Commit
1428d20
Β·
1 Parent(s): d3062ba

fix: correct spelling of 'HuggingMes' across multiple files

Browse files
.env.example CHANGED
@@ -1,5 +1,5 @@
1
  # ============================================================================
2
- # HuggingMess - Hermes Agent on Hugging Face Spaces
3
  # Configuration Reference
4
  # ============================================================================
5
 
@@ -36,8 +36,8 @@ GATEWAY_TOKEN=your_gateway_token_here
36
  # HF token with write access for private Dataset backup
37
  HF_TOKEN=hf_your_token_here
38
 
39
- # Dataset name (default: huggingmess-backup)
40
- # BACKUP_DATASET_NAME=huggingmess-backup
41
 
42
  # Backup interval in seconds (default: 180)
43
  SYNC_INTERVAL=180
 
1
  # ============================================================================
2
+ # HuggingMes - Hermes Agent on Hugging Face Spaces
3
  # Configuration Reference
4
  # ============================================================================
5
 
 
36
  # HF token with write access for private Dataset backup
37
  HF_TOKEN=hf_your_token_here
38
 
39
+ # Dataset name (default: huggingmes-backup)
40
+ # BACKUP_DATASET_NAME=huggingmes-backup
41
 
42
  # Backup interval in seconds (default: 180)
43
  SYNC_INTERVAL=180
CHANGELOG.md CHANGED
@@ -2,7 +2,7 @@
2
 
3
  ## 0.1.0 - 2026-05-03
4
 
5
- - Initial HuggingMess Docker Space wrapper for Nous Research Hermes Agent.
6
  - Added HF Space dashboard, `/health`, `/status`, `/v1/*` proxy, and Telegram webhook proxy.
7
  - Added Cloudflare Worker setup for Telegram Bot API base URL proxying.
8
  - Added private HF Dataset backup and restore for Hermes state.
 
2
 
3
  ## 0.1.0 - 2026-05-03
4
 
5
+ - Initial HuggingMes Docker Space wrapper for Nous Research Hermes Agent.
6
  - Added HF Space dashboard, `/health`, `/status`, `/v1/*` proxy, and Telegram webhook proxy.
7
  - Added Cloudflare Worker setup for Telegram Bot API base URL proxying.
8
  - Added private HF Dataset backup and restore for Hermes state.
CONTRIBUTING.md CHANGED
@@ -1,6 +1,6 @@
1
  # Contributing
2
 
3
- Thanks for improving HuggingMess.
4
 
5
  ## Local Checks
6
 
 
1
  # Contributing
2
 
3
+ Thanks for improving HuggingMes.
4
 
5
  ## Local Checks
6
 
Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- # HuggingMess - Hermes Agent Gateway for Hugging Face Spaces
2
 
3
  ARG HERMES_AGENT_VERSION=latest
4
  FROM nousresearch/hermes-agent:${HERMES_AGENT_VERSION}
@@ -13,20 +13,20 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
13
  && rm -rf /var/lib/apt/lists/* \
14
  && uv pip install --python /opt/hermes/.venv/bin/python --no-cache-dir huggingface_hub
15
 
16
- COPY --chown=hermes:hermes start.sh /opt/huggingmess/start.sh
17
- COPY --chown=hermes:hermes health-server.js /opt/huggingmess/health-server.js
18
- COPY --chown=hermes:hermes hermes-sync.py /opt/huggingmess/hermes-sync.py
19
- COPY --chown=hermes:hermes cloudflare-proxy-setup.py /opt/huggingmess/cloudflare-proxy-setup.py
20
- COPY --chown=hermes:hermes cloudflare-keepalive-setup.py /opt/huggingmess/cloudflare-keepalive-setup.py
21
 
22
  RUN chmod +x \
23
- /opt/huggingmess/start.sh \
24
- /opt/huggingmess/hermes-sync.py \
25
- /opt/huggingmess/cloudflare-proxy-setup.py \
26
- /opt/huggingmess/cloudflare-keepalive-setup.py
27
 
28
  ENV HERMES_HOME=/opt/data \
29
- HUGGINGMESS_APP_DIR=/opt/huggingmess \
30
  HERMES_AGENT_VERSION=${HERMES_AGENT_VERSION} \
31
  PYTHONUNBUFFERED=1
32
 
@@ -35,4 +35,4 @@ EXPOSE 7861
35
  HEALTHCHECK --interval=30s --timeout=5s --start-period=90s \
36
  CMD curl -fsS http://localhost:7861/health || exit 1
37
 
38
- CMD ["/opt/huggingmess/start.sh"]
 
1
+ # HuggingMes - Hermes Agent Gateway for Hugging Face Spaces
2
 
3
  ARG HERMES_AGENT_VERSION=latest
4
  FROM nousresearch/hermes-agent:${HERMES_AGENT_VERSION}
 
13
  && rm -rf /var/lib/apt/lists/* \
14
  && uv pip install --python /opt/hermes/.venv/bin/python --no-cache-dir huggingface_hub
15
 
16
+ COPY --chown=hermes:hermes start.sh /opt/huggingmes/start.sh
17
+ COPY --chown=hermes:hermes health-server.js /opt/huggingmes/health-server.js
18
+ COPY --chown=hermes:hermes hermes-sync.py /opt/huggingmes/hermes-sync.py
19
+ COPY --chown=hermes:hermes cloudflare-proxy-setup.py /opt/huggingmes/cloudflare-proxy-setup.py
20
+ COPY --chown=hermes:hermes cloudflare-keepalive-setup.py /opt/huggingmes/cloudflare-keepalive-setup.py
21
 
22
  RUN chmod +x \
23
+ /opt/huggingmes/start.sh \
24
+ /opt/huggingmes/hermes-sync.py \
25
+ /opt/huggingmes/cloudflare-proxy-setup.py \
26
+ /opt/huggingmes/cloudflare-keepalive-setup.py
27
 
28
  ENV HERMES_HOME=/opt/data \
29
+ HUGGINGMES_APP_DIR=/opt/huggingmes \
30
  HERMES_AGENT_VERSION=${HERMES_AGENT_VERSION} \
31
  PYTHONUNBUFFERED=1
32
 
 
35
  HEALTHCHECK --interval=30s --timeout=5s --start-period=90s \
36
  CMD curl -fsS http://localhost:7861/health || exit 1
37
 
38
+ CMD ["/opt/huggingmes/start.sh"]
README.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
- title: HuggingMess
3
- emoji: πŸ“š
4
  colorFrom: blue
5
  colorTo: indigo
6
  sdk: docker
@@ -30,7 +30,7 @@ secrets:
30
  [![HF Space](https://img.shields.io/badge/πŸ€—%20HuggingFace-Space-blue?style=flat-square)](https://huggingface.co/spaces)
31
  [![Hermes](https://img.shields.io/badge/Hermes-Agent-indigo?style=flat-square)](https://github.com/NousResearch/hermes-agent)
32
 
33
- **Self-hosted Hermes AI agent gateway β€” free, no server needed.** HuggingMess runs [Nous Research Hermes Agent](https://github.com/NousResearch/hermes-agent) on HuggingFace Spaces, providing a 24/7 personal AI assistant. It includes a premium management dashboard, automatic persistent backup to HF Datasets, and built-in connectivity fixes to bypass platform restrictions. Deploy in minutes on the free HF Spaces tier with full data persistence.
34
 
35
  ## Table of Contents
36
 
@@ -62,7 +62,7 @@ secrets:
62
 
63
  ### Step 1: Duplicate this Space
64
 
65
- [![Duplicate this Space](https://huggingface.co/datasets/huggingface/badges/resolve/main/duplicate-this-space-xl.svg)](https://huggingface.co/spaces/somratpro/HuggingMess?duplicate=true)
66
 
67
  ### Step 2: Add Your Secrets
68
 
@@ -74,18 +74,18 @@ Navigate to your new Space's **Settings β†’ Variables and secrets**, and add the
74
 
75
  ### Step 3: Access Your Dashboard
76
 
77
- Once the build is complete, visit your Space's public URL. You will see the HuggingMess management dashboard. Click **Open Hermes UI** and enter your `GATEWAY_TOKEN` to access the agent interface.
78
 
79
  ## πŸ” Access Control
80
 
81
- Hermes' built-in dashboard is local-first. HuggingMess adds a secure wrapper:
82
 
83
  - **Dashboard:** Opening `/app/` requires your `GATEWAY_TOKEN`.
84
  - **API:** Routes under `/v1/*` (OpenAI-compatible) require `Authorization: Bearer <GATEWAY_TOKEN>`.
85
 
86
  ## πŸ€– LLM Providers
87
 
88
- HuggingMess automatically maps your `LLM_MODEL` and `LLM_API_KEY` to the correct Hermes configuration.
89
 
90
  | Provider | Prefix | Example `LLM_MODEL` |
91
  | :--- | :--- | :--- |
@@ -105,16 +105,16 @@ To use Hermes via Telegram:
105
 
106
  ## 🌐 Cloudflare Proxy
107
 
108
- HuggingFace Spaces often block outbound connections to external APIs. HuggingMess handles this automatically:
109
 
110
  1. Add `CLOUDFLARE_WORKERS_TOKEN` as a Space secret.
111
  2. Restart the Space.
112
 
113
- HuggingMess will auto-provision a Worker proxy for Telegram and other restricted traffic, and set up a keep-awake cron.
114
 
115
  ## πŸ’Ύ Backup & Persistence
116
 
117
- Set `HF_TOKEN` with **Write** access to enable backup. HuggingMess syncs all agent data to a private Dataset named `huggingmess-backup` every 180 seconds.
118
 
119
  ## πŸ’“ Staying Alive *(Recommended on Free HF Spaces)*
120
 
 
1
  ---
2
+ title: HuggingMes
3
+ emoji: ⚚
4
  colorFrom: blue
5
  colorTo: indigo
6
  sdk: docker
 
30
  [![HF Space](https://img.shields.io/badge/πŸ€—%20HuggingFace-Space-blue?style=flat-square)](https://huggingface.co/spaces)
31
  [![Hermes](https://img.shields.io/badge/Hermes-Agent-indigo?style=flat-square)](https://github.com/NousResearch/hermes-agent)
32
 
33
+ **Self-hosted Hermes AI agent gateway β€” free, no server needed.** HuggingMes runs [Nous Research Hermes Agent](https://github.com/NousResearch/hermes-agent) on HuggingFace Spaces, providing a 24/7 personal AI assistant. It includes a premium management dashboard, automatic persistent backup to HF Datasets, and built-in connectivity fixes to bypass platform restrictions. Deploy in minutes on the free HF Spaces tier with full data persistence.
34
 
35
  ## Table of Contents
36
 
 
62
 
63
  ### Step 1: Duplicate this Space
64
 
65
+ [![Duplicate this Space](https://huggingface.co/datasets/huggingface/badges/resolve/main/duplicate-this-space-xl.svg)](https://huggingface.co/spaces/somratpro/HuggingMes?duplicate=true)
66
 
67
  ### Step 2: Add Your Secrets
68
 
 
74
 
75
  ### Step 3: Access Your Dashboard
76
 
77
+ Once the build is complete, visit your Space's public URL. You will see the HuggingMes management dashboard. Click **Open Hermes UI** and enter your `GATEWAY_TOKEN` to access the agent interface.
78
 
79
  ## πŸ” Access Control
80
 
81
+ Hermes' built-in dashboard is local-first. HuggingMes adds a secure wrapper:
82
 
83
  - **Dashboard:** Opening `/app/` requires your `GATEWAY_TOKEN`.
84
  - **API:** Routes under `/v1/*` (OpenAI-compatible) require `Authorization: Bearer <GATEWAY_TOKEN>`.
85
 
86
  ## πŸ€– LLM Providers
87
 
88
+ HuggingMes automatically maps your `LLM_MODEL` and `LLM_API_KEY` to the correct Hermes configuration.
89
 
90
  | Provider | Prefix | Example `LLM_MODEL` |
91
  | :--- | :--- | :--- |
 
105
 
106
  ## 🌐 Cloudflare Proxy
107
 
108
+ HuggingFace Spaces often block outbound connections to external APIs. HuggingMes handles this automatically:
109
 
110
  1. Add `CLOUDFLARE_WORKERS_TOKEN` as a Space secret.
111
  2. Restart the Space.
112
 
113
+ HuggingMes will auto-provision a Worker proxy for Telegram and other restricted traffic, and set up a keep-awake cron.
114
 
115
  ## πŸ’Ύ Backup & Persistence
116
 
117
+ Set `HF_TOKEN` with **Write** access to enable backup. HuggingMes syncs all agent data to a private Dataset named `huggingmes-backup` every 180 seconds.
118
 
119
  ## πŸ’“ Staying Alive *(Recommended on Free HF Spaces)*
120
 
SECURITY.md CHANGED
@@ -1,6 +1,6 @@
1
  # Security
2
 
3
- HuggingMess runs a full agent gateway with tool access. Treat the Space and its secrets like a server.
4
 
5
  ## Required Hardening
6
 
 
1
  # Security
2
 
3
+ HuggingMes runs a full agent gateway with tool access. Treat the Space and its secrets like a server.
4
 
5
  ## Required Hardening
6
 
cloudflare-keepalive-setup.py CHANGED
@@ -12,7 +12,7 @@ import urllib.request
12
  from pathlib import Path
13
 
14
  API_BASE = "https://api.cloudflare.com/client/v4"
15
- KEEPALIVE_STATUS_FILE = Path("/tmp/huggingmess-cloudflare-keepalive-status.json")
16
 
17
 
18
  def cf_request(method: str, path: str, token: str, body: bytes | None = None, content_type: str = "application/json"):
@@ -33,7 +33,7 @@ def cf_request(method: str, path: str, token: str, body: bytes | None = None, co
33
  def slugify(value: str) -> str:
34
  cleaned = re.sub(r"[^a-z0-9-]+", "-", value.lower()).strip("-")
35
  cleaned = re.sub(r"-{2,}", "-", cleaned)
36
- return (cleaned or "huggingmess-proxy")[:63].rstrip("-")
37
 
38
 
39
  def get_space_host() -> str:
@@ -56,7 +56,7 @@ def derive_keepalive_worker_name() -> str:
56
  space_host = get_space_host()
57
  if space_host:
58
  return slugify(f"{space_host.replace('.hf.space', '')}-keepalive")
59
- return "huggingmess-keepalive"
60
 
61
 
62
  def render_keepalive_worker(target_url: str) -> str:
@@ -76,7 +76,7 @@ async function ping(source) {{
76
  const response = await fetch(TARGET_URL, {{
77
  method: "GET",
78
  headers: {{
79
- "user-agent": "HuggingMess Cloudflare KeepAlive",
80
  "cache-control": "no-cache"
81
  }},
82
  cf: {{ cacheTtl: 0, cacheEverything: false }}
 
12
  from pathlib import Path
13
 
14
  API_BASE = "https://api.cloudflare.com/client/v4"
15
+ KEEPALIVE_STATUS_FILE = Path("/tmp/huggingmes-cloudflare-keepalive-status.json")
16
 
17
 
18
  def cf_request(method: str, path: str, token: str, body: bytes | None = None, content_type: str = "application/json"):
 
33
  def slugify(value: str) -> str:
34
  cleaned = re.sub(r"[^a-z0-9-]+", "-", value.lower()).strip("-")
35
  cleaned = re.sub(r"-{2,}", "-", cleaned)
36
+ return (cleaned or "huggingmes-proxy")[:63].rstrip("-")
37
 
38
 
39
  def get_space_host() -> str:
 
56
  space_host = get_space_host()
57
  if space_host:
58
  return slugify(f"{space_host.replace('.hf.space', '')}-keepalive")
59
+ return "huggingmes-keepalive"
60
 
61
 
62
  def render_keepalive_worker(target_url: str) -> str:
 
76
  const response = await fetch(TARGET_URL, {{
77
  method: "GET",
78
  headers: {{
79
+ "user-agent": "HuggingMes Cloudflare KeepAlive",
80
  "cache-control": "no-cache"
81
  }},
82
  cf: {{ cacheTtl: 0, cacheEverything: false }}
cloudflare-proxy-setup.py CHANGED
@@ -13,8 +13,8 @@ import urllib.request
13
  from pathlib import Path
14
 
15
  API_BASE = "https://api.cloudflare.com/client/v4"
16
- ENV_FILE = Path("/tmp/huggingmess-cloudflare-proxy.env")
17
- ENV_FILE = Path("/tmp/huggingmess-cloudflare-proxy.env")
18
  DEFAULT_ALLOWED = [
19
  "api.telegram.org",
20
  "discord.com",
@@ -52,7 +52,7 @@ def cf_request(method: str, path: str, token: str, body: bytes | None = None, co
52
  def slugify(value: str) -> str:
53
  cleaned = re.sub(r"[^a-z0-9-]+", "-", value.lower()).strip("-")
54
  cleaned = re.sub(r"-{2,}", "-", cleaned)
55
- return (cleaned or "huggingmess-proxy")[:63].rstrip("-")
56
 
57
 
58
  def derive_worker_name() -> str:
@@ -62,7 +62,7 @@ def derive_worker_name() -> str:
62
  space_host = os.environ.get("SPACE_HOST", "").strip()
63
  if space_host:
64
  return slugify(f"{space_host.replace('.hf.space', '')}-proxy")
65
- return "huggingmess-proxy"
66
 
67
 
68
  def render_worker(secret_value: str, allowed_targets: list[str], allow_proxy_all: bool) -> str:
 
13
  from pathlib import Path
14
 
15
  API_BASE = "https://api.cloudflare.com/client/v4"
16
+ ENV_FILE = Path("/tmp/huggingmes-cloudflare-proxy.env")
17
+ ENV_FILE = Path("/tmp/huggingmes-cloudflare-proxy.env")
18
  DEFAULT_ALLOWED = [
19
  "api.telegram.org",
20
  "discord.com",
 
52
  def slugify(value: str) -> str:
53
  cleaned = re.sub(r"[^a-z0-9-]+", "-", value.lower()).strip("-")
54
  cleaned = re.sub(r"-{2,}", "-", cleaned)
55
+ return (cleaned or "huggingmes-proxy")[:63].rstrip("-")
56
 
57
 
58
  def derive_worker_name() -> str:
 
62
  space_host = os.environ.get("SPACE_HOST", "").strip()
63
  if space_host:
64
  return slugify(f"{space_host.replace('.hf.space', '')}-proxy")
65
+ return "huggingmes-proxy"
66
 
67
 
68
  def render_worker(secret_value: str, allowed_targets: list[str], allow_proxy_all: bool) -> str:
docker-compose.yml CHANGED
@@ -1,10 +1,10 @@
1
  services:
2
- huggingmess:
3
  build:
4
  context: .
5
  args:
6
  HERMES_AGENT_VERSION: ${HERMES_AGENT_VERSION:-latest}
7
- image: huggingmess:local
8
  ports:
9
  - "7861:7861"
10
  environment:
@@ -16,7 +16,7 @@ services:
16
  HF_TOKEN: ${HF_TOKEN:-}
17
  SPACE_HOST: ${SPACE_HOST:-localhost:7861}
18
  volumes:
19
- - huggingmess-data:/opt/data
20
 
21
  volumes:
22
- huggingmess-data:
 
1
  services:
2
+ huggingmes:
3
  build:
4
  context: .
5
  args:
6
  HERMES_AGENT_VERSION: ${HERMES_AGENT_VERSION:-latest}
7
+ image: huggingmes:local
8
  ports:
9
  - "7861:7861"
10
  environment:
 
16
  HF_TOKEN: ${HF_TOKEN:-}
17
  SPACE_HOST: ${SPACE_HOST:-localhost:7861}
18
  volumes:
19
+ - huggingmes-data:/opt/data
20
 
21
  volumes:
22
+ huggingmes-data:
health-server.js CHANGED
@@ -14,11 +14,11 @@ const startTime = Date.now();
14
  const API_SERVER_KEY = process.env.API_SERVER_KEY || "";
15
  const APP_BASE = "/app";
16
  const LOGIN_PATH = "/login";
17
- const SESSION_COOKIE = "huggingmess_session";
18
 
19
- const SYNC_STATUS_FILE = "/tmp/huggingmess-sync-status.json";
20
  const CLOUDFLARE_KEEPALIVE_STATUS_FILE =
21
- "/tmp/huggingmess-cloudflare-keepalive-status.json";
22
 
23
  function canConnect(port, host = GATEWAY_HOST, timeoutMs = 600) {
24
  return new Promise((resolve) => {
@@ -54,7 +54,7 @@ function expectedSessionValue() {
54
  if (!API_SERVER_KEY) return "";
55
  return crypto
56
  .createHmac("sha256", API_SERVER_KEY)
57
- .update("huggingmess-session-v1")
58
  .digest("hex");
59
  }
60
 
@@ -122,7 +122,7 @@ function renderLoginPage(nextPath, errorMessage = "") {
122
  <head>
123
  <meta charset="utf-8" />
124
  <meta name="viewport" content="width=device-width, initial-scale=1" />
125
- <title>HuggingMess Login</title>
126
  <style>
127
  :root { color-scheme: dark; --bg:#10141f; --panel:#171d2b; --line:#293246; --text:#f4f7fb; --muted:#9aa7bd; --bad:#ef4444; --accent:#38bdf8; }
128
  * { box-sizing:border-box; }
@@ -138,7 +138,7 @@ function renderLoginPage(nextPath, errorMessage = "") {
138
  </head>
139
  <body>
140
  <main>
141
- <h1>Open HuggingMess</h1>
142
  <p>Enter the <code>GATEWAY_TOKEN</code> from your Space secrets.</p>
143
  ${errorHtml}
144
  <form method="post" action="${LOGIN_PATH}">
@@ -410,7 +410,9 @@ function renderDashboard(data) {
410
  const telegramDetail = data.telegram.configured
411
  ? `${data.telegram.webhook ? "Webhook" : "Polling"}${data.telegram.proxy ? " via CF proxy" : ""}`
412
  : "Not configured";
413
- const backupDetail = data.backup?.message ? escapeHtml(data.backup.message) : "No status yet";
 
 
414
  const keepAliveDetail = keepaliveConfigured
415
  ? `Pinging <code>${escapeHtml(data.keepalive.targetUrl || "/health")}</code>`
416
  : process.env.CLOUDFLARE_WORKERS_TOKEN
@@ -421,8 +423,13 @@ function renderDashboard(data) {
421
  const tiles = [
422
  renderTile({
423
  title: "Gateway",
424
- value: toneBadge(data.gateway ? "Online" : "Offline", data.gateway ? "ok" : "off"),
425
- detail: data.gateway ? `API on port ${data.ports.gateway}` : `Unreachable`,
 
 
 
 
 
426
  tone: data.gateway ? "ok" : "off",
427
  meta: data.authConfigured ? "Protected" : "Unprotected",
428
  }),
@@ -440,7 +447,10 @@ function renderDashboard(data) {
440
  }),
441
  renderTile({
442
  title: "Telegram",
443
- value: toneBadge(data.telegram.configured ? "Configured" : "Disabled", telegramTone),
 
 
 
444
  detail: telegramDetail,
445
  tone: telegramTone,
446
  }),
@@ -452,7 +462,10 @@ function renderDashboard(data) {
452
  }),
453
  renderTile({
454
  title: "Keep Awake",
455
- value: toneBadge(keepaliveConfigured ? "CF Cron" : keepaliveStatus.toUpperCase(), keepAliveTone),
 
 
 
456
  detail: keepAliveDetail,
457
  tone: keepAliveTone,
458
  }),
@@ -463,7 +476,7 @@ function renderDashboard(data) {
463
  <head>
464
  <meta charset="utf-8" />
465
  <meta name="viewport" content="width=device-width, initial-scale=1" />
466
- <title>HuggingMess</title>
467
  <style>
468
  :root { color-scheme: dark; --bg:#08080f; --panel:#12111b; --panel2:#151421; --line:#26243a; --text:#f6f4ff; --muted:#7f7a9e; --soft:#b8b3d7; --good:#22c55e; --warn:#f5c542; --bad:#fb7185; --accent:#6557df; --accent2:#7c6cf2; }
469
  * { box-sizing:border-box; }
@@ -508,7 +521,7 @@ function renderDashboard(data) {
508
  <body>
509
  <main>
510
  <header>
511
- <h1>HuggingMess</h1>
512
  <div class="subtitle">Self-hosted - Hermes Agent</div>
513
  </header>
514
  <a class="hero-action" href="${APP_BASE}/" target="_blank" rel="noopener noreferrer">Open Hermes Agent -></a>
@@ -641,5 +654,5 @@ const server = http.createServer(async (req, res) => {
641
  });
642
 
643
  server.listen(PORT, "0.0.0.0", () => {
644
- console.log(`HuggingMess dashboard listening on 0.0.0.0:${PORT}`);
645
  });
 
14
  const API_SERVER_KEY = process.env.API_SERVER_KEY || "";
15
  const APP_BASE = "/app";
16
  const LOGIN_PATH = "/login";
17
+ const SESSION_COOKIE = "huggingmes_session";
18
 
19
+ const SYNC_STATUS_FILE = "/tmp/huggingmes-sync-status.json";
20
  const CLOUDFLARE_KEEPALIVE_STATUS_FILE =
21
+ "/tmp/huggingmes-cloudflare-keepalive-status.json";
22
 
23
  function canConnect(port, host = GATEWAY_HOST, timeoutMs = 600) {
24
  return new Promise((resolve) => {
 
54
  if (!API_SERVER_KEY) return "";
55
  return crypto
56
  .createHmac("sha256", API_SERVER_KEY)
57
+ .update("huggingmes-session-v1")
58
  .digest("hex");
59
  }
60
 
 
122
  <head>
123
  <meta charset="utf-8" />
124
  <meta name="viewport" content="width=device-width, initial-scale=1" />
125
+ <title>HuggingMes Login</title>
126
  <style>
127
  :root { color-scheme: dark; --bg:#10141f; --panel:#171d2b; --line:#293246; --text:#f4f7fb; --muted:#9aa7bd; --bad:#ef4444; --accent:#38bdf8; }
128
  * { box-sizing:border-box; }
 
138
  </head>
139
  <body>
140
  <main>
141
+ <h1>Open HuggingMes</h1>
142
  <p>Enter the <code>GATEWAY_TOKEN</code> from your Space secrets.</p>
143
  ${errorHtml}
144
  <form method="post" action="${LOGIN_PATH}">
 
410
  const telegramDetail = data.telegram.configured
411
  ? `${data.telegram.webhook ? "Webhook" : "Polling"}${data.telegram.proxy ? " via CF proxy" : ""}`
412
  : "Not configured";
413
+ const backupDetail = data.backup?.message
414
+ ? escapeHtml(data.backup.message)
415
+ : "No status yet";
416
  const keepAliveDetail = keepaliveConfigured
417
  ? `Pinging <code>${escapeHtml(data.keepalive.targetUrl || "/health")}</code>`
418
  : process.env.CLOUDFLARE_WORKERS_TOKEN
 
423
  const tiles = [
424
  renderTile({
425
  title: "Gateway",
426
+ value: toneBadge(
427
+ data.gateway ? "Online" : "Offline",
428
+ data.gateway ? "ok" : "off",
429
+ ),
430
+ detail: data.gateway
431
+ ? `API on port ${data.ports.gateway}`
432
+ : `Unreachable`,
433
  tone: data.gateway ? "ok" : "off",
434
  meta: data.authConfigured ? "Protected" : "Unprotected",
435
  }),
 
447
  }),
448
  renderTile({
449
  title: "Telegram",
450
+ value: toneBadge(
451
+ data.telegram.configured ? "Configured" : "Disabled",
452
+ telegramTone,
453
+ ),
454
  detail: telegramDetail,
455
  tone: telegramTone,
456
  }),
 
462
  }),
463
  renderTile({
464
  title: "Keep Awake",
465
+ value: toneBadge(
466
+ keepaliveConfigured ? "CF Cron" : keepaliveStatus.toUpperCase(),
467
+ keepAliveTone,
468
+ ),
469
  detail: keepAliveDetail,
470
  tone: keepAliveTone,
471
  }),
 
476
  <head>
477
  <meta charset="utf-8" />
478
  <meta name="viewport" content="width=device-width, initial-scale=1" />
479
+ <title>HuggingMes</title>
480
  <style>
481
  :root { color-scheme: dark; --bg:#08080f; --panel:#12111b; --panel2:#151421; --line:#26243a; --text:#f6f4ff; --muted:#7f7a9e; --soft:#b8b3d7; --good:#22c55e; --warn:#f5c542; --bad:#fb7185; --accent:#6557df; --accent2:#7c6cf2; }
482
  * { box-sizing:border-box; }
 
521
  <body>
522
  <main>
523
  <header>
524
+ <h1>HuggingMes</h1>
525
  <div class="subtitle">Self-hosted - Hermes Agent</div>
526
  </header>
527
  <a class="hero-action" href="${APP_BASE}/" target="_blank" rel="noopener noreferrer">Open Hermes Agent -></a>
 
654
  });
655
 
656
  server.listen(PORT, "0.0.0.0", () => {
657
+ console.log(`HuggingMes dashboard listening on 0.0.0.0:${PORT}`);
658
  });
hermes-sync.py CHANGED
@@ -1,5 +1,5 @@
1
  #!/usr/bin/env python3
2
- """HuggingMess Hermes state backup via Hugging Face Datasets."""
3
 
4
  import hashlib
5
  import json
@@ -22,13 +22,13 @@ from huggingface_hub.errors import HfHubHTTPError, RepositoryNotFoundError
22
  logging.getLogger("huggingface_hub").setLevel(logging.ERROR)
23
 
24
  HERMES_HOME = Path(os.environ.get("HERMES_HOME", "/opt/data"))
25
- STATUS_FILE = Path("/tmp/huggingmess-sync-status.json")
26
  INTERVAL = int(os.environ.get("SYNC_INTERVAL", "180"))
27
  INITIAL_DELAY = int(os.environ.get("SYNC_START_DELAY", "10"))
28
  HF_TOKEN = os.environ.get("HF_TOKEN", "").strip()
29
  HF_USERNAME = os.environ.get("HF_USERNAME", "").strip()
30
  SPACE_AUTHOR_NAME = os.environ.get("SPACE_AUTHOR_NAME", "").strip()
31
- BACKUP_DATASET_NAME = os.environ.get("BACKUP_DATASET_NAME", "huggingmess-backup").strip()
32
  INCLUDE_ENV = os.environ.get("SYNC_INCLUDE_ENV", "").strip().lower() in {"1", "true", "yes"}
33
  MAX_FILE_SIZE_BYTES = int(os.environ.get("SYNC_MAX_FILE_BYTES", str(50 * 1024 * 1024)))
34
 
@@ -142,7 +142,7 @@ def fingerprint_dir(root: Path) -> str:
142
 
143
 
144
  def create_snapshot_dir(source_root: Path) -> Path:
145
- staging_root = Path(tempfile.mkdtemp(prefix="huggingmess-sync-"))
146
  for path in sorted(source_root.rglob("*")):
147
  rel = path.relative_to(source_root)
148
  rel_posix = rel.as_posix()
@@ -237,7 +237,7 @@ def sync_once(last_fingerprint: str | None = None, last_marker: tuple[int, int,
237
  repo_id=repo_id,
238
  repo_type="dataset",
239
  token=HF_TOKEN,
240
- commit_message=f"HuggingMess sync {time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())}",
241
  ignore_patterns=[".git/*", ".git"],
242
  )
243
  finally:
 
1
  #!/usr/bin/env python3
2
+ """HuggingMes Hermes state backup via Hugging Face Datasets."""
3
 
4
  import hashlib
5
  import json
 
22
  logging.getLogger("huggingface_hub").setLevel(logging.ERROR)
23
 
24
  HERMES_HOME = Path(os.environ.get("HERMES_HOME", "/opt/data"))
25
+ STATUS_FILE = Path("/tmp/huggingmes-sync-status.json")
26
  INTERVAL = int(os.environ.get("SYNC_INTERVAL", "180"))
27
  INITIAL_DELAY = int(os.environ.get("SYNC_START_DELAY", "10"))
28
  HF_TOKEN = os.environ.get("HF_TOKEN", "").strip()
29
  HF_USERNAME = os.environ.get("HF_USERNAME", "").strip()
30
  SPACE_AUTHOR_NAME = os.environ.get("SPACE_AUTHOR_NAME", "").strip()
31
+ BACKUP_DATASET_NAME = os.environ.get("BACKUP_DATASET_NAME", "huggingmes-backup").strip()
32
  INCLUDE_ENV = os.environ.get("SYNC_INCLUDE_ENV", "").strip().lower() in {"1", "true", "yes"}
33
  MAX_FILE_SIZE_BYTES = int(os.environ.get("SYNC_MAX_FILE_BYTES", str(50 * 1024 * 1024)))
34
 
 
142
 
143
 
144
  def create_snapshot_dir(source_root: Path) -> Path:
145
+ staging_root = Path(tempfile.mkdtemp(prefix="huggingmes-sync-"))
146
  for path in sorted(source_root.rglob("*")):
147
  rel = path.relative_to(source_root)
148
  rel_posix = rel.as_posix()
 
237
  repo_id=repo_id,
238
  repo_type="dataset",
239
  token=HF_TOKEN,
240
+ commit_message=f"HuggingMes sync {time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())}",
241
  ignore_patterns=[".git/*", ".git"],
242
  )
243
  finally:
start.sh CHANGED
@@ -4,19 +4,19 @@ set -euo pipefail
4
  umask 0077
5
 
6
  # ════════════════════════════════════════════════════════════════
7
- # HuggingMess β€” Hermes Gateway for HF Spaces
8
  # ════════════════════════════════════════════════════════════════
9
 
10
  # ── Startup Banner ──
11
- APP_DIR="${HUGGINGMESS_APP_DIR:-/opt/huggingmess}"
12
  HERMES_HOME="${HERMES_HOME:-/opt/data}"
13
  PUBLIC_PORT="${PORT:-7861}"
14
  GATEWAY_API_PORT="${API_SERVER_PORT:-8642}"
15
  DASHBOARD_PORT="${DASHBOARD_PORT:-9119}"
16
  TELEGRAM_WEBHOOK_PORT="${TELEGRAM_WEBHOOK_PORT:-8765}"
17
  SYNC_INTERVAL="${SYNC_INTERVAL:-180}"
18
- BACKUP_DATASET="${BACKUP_DATASET_NAME:-huggingmess-backup}"
19
- CF_PROXY_ENV_FILE="/tmp/huggingmess-cloudflare-proxy.env"
20
 
21
  export HERMES_HOME
22
  export API_SERVER_ENABLED="${API_SERVER_ENABLED:-true}"
@@ -27,7 +27,7 @@ export TELEGRAM_WEBHOOK_PORT
27
 
28
  echo ""
29
  echo " ╔══════════════════════════════════════════╗"
30
- echo " β•‘ πŸ’¬ HuggingMess Hermes Gateway β•‘"
31
  echo " β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"
32
  echo ""
33
 
@@ -85,7 +85,7 @@ if [ -n "${TELEGRAM_BOT_TOKEN:-}" ] && [ -n "${SPACE_HOST:-}" ] && [ -z "${TELEG
85
  fi
86
 
87
  if [ -n "${TELEGRAM_WEBHOOK_URL:-}" ] && [ -z "${TELEGRAM_WEBHOOK_SECRET:-}" ]; then
88
- SECRET_FILE="$HERMES_HOME/.huggingmess-telegram-webhook-secret"
89
  if [ -f "$SECRET_FILE" ]; then
90
  export TELEGRAM_WEBHOOK_SECRET
91
  TELEGRAM_WEBHOOK_SECRET="$(cat "$SECRET_FILE")"
@@ -274,7 +274,7 @@ echo ""
274
 
275
  # ── Trap SIGTERM for graceful shutdown ──
276
  graceful_shutdown() {
277
- echo "Shutting down HuggingMess..."
278
  if [ -n "${HF_TOKEN:-}" ]; then
279
  python3 "$APP_DIR/hermes-sync.py" sync-once || echo "Warning: shutdown sync failed."
280
  fi
@@ -293,7 +293,7 @@ import json, os, urllib.request
293
  body = json.dumps({
294
  "event": "restart",
295
  "status": "success",
296
- "message": "HuggingMess Hermes gateway has started.",
297
  "model": os.environ.get("MODEL_FOR_CONFIG", ""),
298
  }).encode()
299
  req = urllib.request.Request(os.environ["WEBHOOK_URL"], data=body, method="POST", headers={"Content-Type": "application/json"})
 
4
  umask 0077
5
 
6
  # ════════════════════════════════════════════════════════════════
7
+ # HuggingMes β€” Hermes Gateway for HF Spaces
8
  # ════════════════════════════════════════════════════════════════
9
 
10
  # ── Startup Banner ──
11
+ APP_DIR="${HUGGINGMES_APP_DIR:-/opt/huggingmes}"
12
  HERMES_HOME="${HERMES_HOME:-/opt/data}"
13
  PUBLIC_PORT="${PORT:-7861}"
14
  GATEWAY_API_PORT="${API_SERVER_PORT:-8642}"
15
  DASHBOARD_PORT="${DASHBOARD_PORT:-9119}"
16
  TELEGRAM_WEBHOOK_PORT="${TELEGRAM_WEBHOOK_PORT:-8765}"
17
  SYNC_INTERVAL="${SYNC_INTERVAL:-180}"
18
+ BACKUP_DATASET="${BACKUP_DATASET_NAME:-huggingmes-backup}"
19
+ CF_PROXY_ENV_FILE="/tmp/huggingmes-cloudflare-proxy.env"
20
 
21
  export HERMES_HOME
22
  export API_SERVER_ENABLED="${API_SERVER_ENABLED:-true}"
 
27
 
28
  echo ""
29
  echo " ╔══════════════════════════════════════════╗"
30
+ echo " β•‘ πŸ’¬ HuggingMes Hermes Gateway β•‘"
31
  echo " β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"
32
  echo ""
33
 
 
85
  fi
86
 
87
  if [ -n "${TELEGRAM_WEBHOOK_URL:-}" ] && [ -z "${TELEGRAM_WEBHOOK_SECRET:-}" ]; then
88
+ SECRET_FILE="$HERMES_HOME/.huggingmes-telegram-webhook-secret"
89
  if [ -f "$SECRET_FILE" ]; then
90
  export TELEGRAM_WEBHOOK_SECRET
91
  TELEGRAM_WEBHOOK_SECRET="$(cat "$SECRET_FILE")"
 
274
 
275
  # ── Trap SIGTERM for graceful shutdown ──
276
  graceful_shutdown() {
277
+ echo "Shutting down HuggingMes..."
278
  if [ -n "${HF_TOKEN:-}" ]; then
279
  python3 "$APP_DIR/hermes-sync.py" sync-once || echo "Warning: shutdown sync failed."
280
  fi
 
293
  body = json.dumps({
294
  "event": "restart",
295
  "status": "success",
296
+ "message": "HuggingMes Hermes gateway has started.",
297
  "model": os.environ.get("MODEL_FOR_CONFIG", ""),
298
  }).encode()
299
  req = urllib.request.Request(os.environ["WEBHOOK_URL"], data=body, method="POST", headers={"Content-Type": "application/json"})