somratpro commited on
Commit
c3abaa8
·
1 Parent(s): c7e4cd8

chore: update README metadata and apply consistent code style to health server

Browse files
Files changed (2) hide show
  1. README.md +18 -5
  2. health-server.js +73 -66
README.md CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
  title: HuggingClip
3
- emoji: 🔗
4
- colorFrom: purple
5
- colorTo: blue
6
  sdk: docker
7
  app_port: 7861
8
  pinned: true
@@ -20,13 +20,14 @@ secrets:
20
  description: Optional Cloudflare account ID (required if using Cloudflare proxy).
21
  ---
22
 
23
- # 🔗 HuggingClip
24
 
25
  Paperclip AI Agent Orchestration Platform running on Hugging Face Spaces.
26
 
27
  Deploy your own instance of [Paperclip](https://paperclip.ing/) — the open-source platform for orchestrating AI agents to run autonomous businesses — on Hugging Face Spaces with automatic persistent backup to Hugging Face Datasets.
28
 
29
  **Features:**
 
30
  - ✅ Run Paperclip on HF Spaces (free tier compatible)
31
  - ✅ Automatic database backup to HF Dataset (survives restarts)
32
  - ✅ Health monitoring dashboard with real-time status
@@ -41,6 +42,7 @@ Deploy your own instance of [Paperclip](https://paperclip.ing/) — the open-sou
41
  [Deploy to Hugging Face Spaces](https://huggingface.co/new-space?template=somratpro/HuggingClip)
42
 
43
  Or manually:
 
44
  1. Create a new Space on [Hugging Face](https://huggingface.co/new-space)
45
  2. Choose **Docker** as the runtime
46
  3. Copy this repository as the source
@@ -50,6 +52,7 @@ Or manually:
50
  ### Local Development
51
 
52
  #### Prerequisites
 
53
  - Docker & Docker Compose
54
  - Node.js 20+ (for direct testing)
55
  - PostgreSQL 13+ (if running outside Docker)
@@ -185,6 +188,7 @@ docker-compose up -d
185
  Access the health monitoring dashboard at: `http://your-space-url/`
186
 
187
  **Shows:**
 
188
  - Paperclip service status (running/down)
189
  - Database health & last backup timestamp
190
  - System uptime & start time
@@ -195,6 +199,7 @@ Access the health monitoring dashboard at: `http://your-space-url/`
195
  Full Paperclip interface at: `http://your-space-url/app/`
196
 
197
  **Features:**
 
198
  - Create companies and organizational structures
199
  - Recruit AI agents with specific roles
200
  - Define tasks and monitor execution
@@ -207,6 +212,7 @@ Full Paperclip interface at: `http://your-space-url/app/`
207
  Direct API access at: `http://your-space-url/api/*`
208
 
209
  Examples:
 
210
  ```bash
211
  # Get API status
212
  curl http://localhost:7861/health
@@ -252,6 +258,7 @@ python3 /app/paperclip-sync.py restore
252
  **Problem**: "Cannot connect to PostgreSQL"
253
 
254
  **Solution:**
 
255
  1. Check DATABASE_URL is correct: `postgres://user:pass@host:port/db`
256
  2. Verify PostgreSQL is running: `docker ps | grep postgres`
257
  3. Check credentials in DATABASE_URL match PostgreSQL setup
@@ -262,6 +269,7 @@ python3 /app/paperclip-sync.py restore
262
  **Problem**: "Sync status shows error"
263
 
264
  **Solution:**
 
265
  1. Verify `HF_TOKEN` is set and valid
266
  2. Check HF Dataset is created: `huggingface-cli repo info datasets/your-username/paperclip-backup`
267
  3. Look at container logs: `docker logs huggingclip-app`
@@ -269,9 +277,10 @@ python3 /app/paperclip-sync.py restore
269
 
270
  ### Paperclip Not Accessible
271
 
272
- **Problem**: Can't reach http://localhost:7861/app/
273
 
274
  **Solution:**
 
275
  1. Check container is running: `docker ps`
276
  2. Check ports are exposed: `docker port huggingclip-app`
277
  3. Verify port 3100 is not blocked
@@ -283,6 +292,7 @@ python3 /app/paperclip-sync.py restore
283
  **Problem**: Container exits repeatedly
284
 
285
  **Solution:**
 
286
  1. Check logs: `docker logs --tail=100 huggingclip-app`
287
  2. Common causes:
288
  - Invalid DATABASE_URL
@@ -296,6 +306,7 @@ python3 /app/paperclip-sync.py restore
296
  **Problem**: "Killed" message or container restarts
297
 
298
  **Solution:**
 
299
  1. HF Spaces free tier: 2 vCPU, 16GB RAM, 50GB storage
300
  2. Reduce backup interval: `SYNC_INTERVAL=600` (every 10 min instead of 3)
301
  3. Reduce database size: Archive old agent runs and conversations
@@ -363,6 +374,7 @@ python3 /app/paperclip-sync.py restore
363
  HuggingClip stores only the **latest backup** in HF Dataset (`snapshots/latest.tar.gz`).
364
 
365
  **To keep multiple backups manually:**
 
366
  ```bash
367
  # Download backup from HF
368
  huggingface-cli download datasets/your-username/paperclip-backup \
@@ -393,6 +405,7 @@ curl -s http://localhost:7861/health | jq .
393
  ```
394
 
395
  Response includes:
 
396
  - Service uptime
397
  - Database status
398
  - Last backup timestamp
 
1
  ---
2
  title: HuggingClip
3
+ emoji: 📎
4
+ colorFrom: gray
5
+ colorTo: black
6
  sdk: docker
7
  app_port: 7861
8
  pinned: true
 
20
  description: Optional Cloudflare account ID (required if using Cloudflare proxy).
21
  ---
22
 
23
+ # HuggingClip
24
 
25
  Paperclip AI Agent Orchestration Platform running on Hugging Face Spaces.
26
 
27
  Deploy your own instance of [Paperclip](https://paperclip.ing/) — the open-source platform for orchestrating AI agents to run autonomous businesses — on Hugging Face Spaces with automatic persistent backup to Hugging Face Datasets.
28
 
29
  **Features:**
30
+
31
  - ✅ Run Paperclip on HF Spaces (free tier compatible)
32
  - ✅ Automatic database backup to HF Dataset (survives restarts)
33
  - ✅ Health monitoring dashboard with real-time status
 
42
  [Deploy to Hugging Face Spaces](https://huggingface.co/new-space?template=somratpro/HuggingClip)
43
 
44
  Or manually:
45
+
46
  1. Create a new Space on [Hugging Face](https://huggingface.co/new-space)
47
  2. Choose **Docker** as the runtime
48
  3. Copy this repository as the source
 
52
  ### Local Development
53
 
54
  #### Prerequisites
55
+
56
  - Docker & Docker Compose
57
  - Node.js 20+ (for direct testing)
58
  - PostgreSQL 13+ (if running outside Docker)
 
188
  Access the health monitoring dashboard at: `http://your-space-url/`
189
 
190
  **Shows:**
191
+
192
  - Paperclip service status (running/down)
193
  - Database health & last backup timestamp
194
  - System uptime & start time
 
199
  Full Paperclip interface at: `http://your-space-url/app/`
200
 
201
  **Features:**
202
+
203
  - Create companies and organizational structures
204
  - Recruit AI agents with specific roles
205
  - Define tasks and monitor execution
 
212
  Direct API access at: `http://your-space-url/api/*`
213
 
214
  Examples:
215
+
216
  ```bash
217
  # Get API status
218
  curl http://localhost:7861/health
 
258
  **Problem**: "Cannot connect to PostgreSQL"
259
 
260
  **Solution:**
261
+
262
  1. Check DATABASE_URL is correct: `postgres://user:pass@host:port/db`
263
  2. Verify PostgreSQL is running: `docker ps | grep postgres`
264
  3. Check credentials in DATABASE_URL match PostgreSQL setup
 
269
  **Problem**: "Sync status shows error"
270
 
271
  **Solution:**
272
+
273
  1. Verify `HF_TOKEN` is set and valid
274
  2. Check HF Dataset is created: `huggingface-cli repo info datasets/your-username/paperclip-backup`
275
  3. Look at container logs: `docker logs huggingclip-app`
 
277
 
278
  ### Paperclip Not Accessible
279
 
280
+ **Problem**: Can't reach <http://localhost:7861/app/>
281
 
282
  **Solution:**
283
+
284
  1. Check container is running: `docker ps`
285
  2. Check ports are exposed: `docker port huggingclip-app`
286
  3. Verify port 3100 is not blocked
 
292
  **Problem**: Container exits repeatedly
293
 
294
  **Solution:**
295
+
296
  1. Check logs: `docker logs --tail=100 huggingclip-app`
297
  2. Common causes:
298
  - Invalid DATABASE_URL
 
306
  **Problem**: "Killed" message or container restarts
307
 
308
  **Solution:**
309
+
310
  1. HF Spaces free tier: 2 vCPU, 16GB RAM, 50GB storage
311
  2. Reduce backup interval: `SYNC_INTERVAL=600` (every 10 min instead of 3)
312
  3. Reduce database size: Archive old agent runs and conversations
 
374
  HuggingClip stores only the **latest backup** in HF Dataset (`snapshots/latest.tar.gz`).
375
 
376
  **To keep multiple backups manually:**
377
+
378
  ```bash
379
  # Download backup from HF
380
  huggingface-cli download datasets/your-username/paperclip-backup \
 
405
  ```
406
 
407
  Response includes:
408
+
409
  - Service uptime
410
  - Database status
411
  - Last backup timestamp
health-server.js CHANGED
@@ -1,26 +1,26 @@
1
- const express = require('express');
2
- const cors = require('cors');
3
- const morgan = require('morgan');
4
- const fs = require('fs');
5
- const path = require('path');
6
- const { promisify } = require('util');
7
- const http = require('http');
8
 
9
  const app = express();
10
  const PORT = process.env.PORT || 7861;
11
- const PAPERCLIP_HOST = process.env.HOST || '127.0.0.1';
12
  const PAPERCLIP_PORT = 3100;
13
 
14
  // Middleware
15
  app.use(cors());
16
- app.use(morgan('combined'));
17
  app.use(express.json());
18
  app.use(express.urlencoded({ extended: true }));
19
 
20
  // ============================================================================
21
  // Health Check Endpoint
22
  // ============================================================================
23
- app.get('/health', async (req, res) => {
24
  try {
25
  const syncStatus = readSyncStatus();
26
  const now = Math.floor(Date.now() / 1000);
@@ -30,38 +30,43 @@ app.get('/health', async (req, res) => {
30
  const paperclipStatus = await checkPaperclipHealth();
31
 
32
  res.status(200).json({
33
- status: 'healthy',
34
  timestamp: new Date().toISOString(),
35
  uptime: Math.floor(uptime),
36
  services: {
37
  healthServer: {
38
- status: 'running',
39
  port: PORT,
40
- uptime: Math.floor(uptime)
41
  },
42
  paperclip: {
43
  status: paperclipStatus.status,
44
  port: PAPERCLIP_PORT,
45
- url: `http://${PAPERCLIP_HOST}:${PAPERCLIP_PORT}`
46
  },
47
  database: {
48
- status: syncStatus.db_status || 'unknown',
49
  lastSync: syncStatus.last_sync_time || null,
50
- lastSyncError: syncStatus.last_error || null
51
- }
52
  },
53
  backup: {
54
- enabled: process.env.SYNC_DISABLED !== 'true',
55
  interval: process.env.SYNC_INTERVAL || 180,
56
  lastSync: syncStatus.last_sync_time,
57
- nextSync: syncStatus.last_sync_time ? new Date((syncStatus.last_sync_time + (parseInt(process.env.SYNC_INTERVAL || 180) * 1000))).toISOString() : null
58
- }
 
 
 
 
 
59
  });
60
  } catch (error) {
61
  res.status(503).json({
62
- status: 'unhealthy',
63
  error: error.message,
64
- timestamp: new Date().toISOString()
65
  });
66
  }
67
  });
@@ -69,15 +74,15 @@ app.get('/health', async (req, res) => {
69
  // ============================================================================
70
  // Dashboard Route
71
  // ============================================================================
72
- app.get('/', (req, res) => {
73
  res.send(getDashboardHTML());
74
  });
75
 
76
- app.get('/dashboard/', (req, res) => {
77
  res.send(getDashboardHTML());
78
  });
79
 
80
- app.get('/dashboard/status', (req, res) => {
81
  const syncStatus = readSyncStatus();
82
  const uptime = process.uptime();
83
 
@@ -86,21 +91,21 @@ app.get('/dashboard/status', (req, res) => {
86
  startTime: new Date(Date.now() - uptime * 1000).toISOString(),
87
  syncStatus: syncStatus,
88
  environment: {
89
- syncDisabled: process.env.SYNC_DISABLED === 'true',
90
  syncInterval: process.env.SYNC_INTERVAL || 180,
91
- paperclipHome: process.env.PAPERCLIP_HOME || '/paperclip'
92
- }
93
  });
94
  });
95
 
96
  // ============================================================================
97
  // UptimeRobot Setup Route
98
  // ============================================================================
99
- app.post('/dashboard/uptimerobot/setup', (req, res) => {
100
  const { webhookUrl } = req.body;
101
 
102
  if (!webhookUrl) {
103
- return res.status(400).json({ error: 'webhookUrl required' });
104
  }
105
 
106
  // Store webhook URL in environment or file
@@ -108,8 +113,8 @@ app.post('/dashboard/uptimerobot/setup', (req, res) => {
108
 
109
  res.json({
110
  success: true,
111
- message: 'UptimeRobot webhook configured',
112
- details: 'Health checks will now notify UptimeRobot to prevent sleep'
113
  });
114
  });
115
 
@@ -118,8 +123,8 @@ app.post('/dashboard/uptimerobot/setup', (req, res) => {
118
  // ============================================================================
119
 
120
  // Proxy all /app/* requests to Paperclip
121
- app.all('/app/*', async (req, res) => {
122
- const targetPath = req.path.replace('/app', '') || '/';
123
  const targetUrl = `http://${PAPERCLIP_HOST}:${PAPERCLIP_PORT}${targetPath}`;
124
 
125
  try {
@@ -127,11 +132,11 @@ app.all('/app/*', async (req, res) => {
127
  req.method,
128
  targetUrl,
129
  req.headers,
130
- req.body
131
  );
132
 
133
  // Copy response headers
134
- Object.keys(response.headers).forEach(key => {
135
  res.setHeader(key, response.headers[key]);
136
  });
137
 
@@ -139,14 +144,14 @@ app.all('/app/*', async (req, res) => {
139
  } catch (error) {
140
  console.error(`Proxy error: ${error.message}`);
141
  res.status(503).json({
142
- error: 'Paperclip service unavailable',
143
- details: error.message
144
  });
145
  }
146
  });
147
 
148
  // Proxy all /api/* requests to Paperclip
149
- app.all('/api/*', async (req, res) => {
150
  const targetPath = req.path;
151
  const targetUrl = `http://${PAPERCLIP_HOST}:${PAPERCLIP_PORT}${targetPath}`;
152
 
@@ -155,10 +160,10 @@ app.all('/api/*', async (req, res) => {
155
  req.method,
156
  targetUrl,
157
  req.headers,
158
- req.body
159
  );
160
 
161
- Object.keys(response.headers).forEach(key => {
162
  res.setHeader(key, response.headers[key]);
163
  });
164
 
@@ -166,8 +171,8 @@ app.all('/api/*', async (req, res) => {
166
  } catch (error) {
167
  console.error(`API proxy error: ${error.message}`);
168
  res.status(503).json({
169
- error: 'Paperclip API unavailable',
170
- details: error.message
171
  });
172
  }
173
  });
@@ -175,7 +180,7 @@ app.all('/api/*', async (req, res) => {
175
  // ============================================================================
176
  // Default Route - Redirect to Dashboard
177
  // ============================================================================
178
- app.get('/', (req, res) => {
179
  res.send(getDashboardHTML());
180
  });
181
 
@@ -185,19 +190,19 @@ app.get('/', (req, res) => {
185
 
186
  function readSyncStatus() {
187
  try {
188
- if (fs.existsSync('/tmp/sync-status.json')) {
189
- const data = fs.readFileSync('/tmp/sync-status.json', 'utf8');
190
  return JSON.parse(data);
191
  }
192
  } catch (error) {
193
- console.error('Error reading sync status:', error.message);
194
  }
195
 
196
  return {
197
- db_status: 'unknown',
198
  last_sync_time: null,
199
  last_error: null,
200
- sync_count: 0
201
  };
202
  }
203
 
@@ -206,16 +211,18 @@ function checkPaperclipHealth() {
206
  const healthUrl = `http://${PAPERCLIP_HOST}:${PAPERCLIP_PORT}/health`;
207
 
208
  const timeout = setTimeout(() => {
209
- resolve({ status: 'unreachable', reason: 'timeout' });
210
  }, 5000);
211
 
212
- http.get(healthUrl, (res) => {
213
- clearTimeout(timeout);
214
- resolve({ status: 'running', statusCode: res.statusCode });
215
- }).on('error', (err) => {
216
- clearTimeout(timeout);
217
- resolve({ status: 'unreachable', reason: err.message });
218
- });
 
 
219
  });
220
  }
221
 
@@ -225,28 +232,28 @@ function proxyRequest(method, url, headers, body) {
225
  method,
226
  headers: {
227
  ...headers,
228
- 'host': `${PAPERCLIP_HOST}:${PAPERCLIP_PORT}`
229
  },
230
- timeout: 30000
231
  };
232
 
233
  const req = http.request(url, options, (res) => {
234
- let data = '';
235
 
236
- res.on('data', (chunk) => {
237
  data += chunk;
238
  });
239
 
240
- res.on('end', () => {
241
  resolve({
242
  statusCode: res.statusCode,
243
  headers: res.headers,
244
- body: data
245
  });
246
  });
247
  });
248
 
249
- req.on('error', (err) => {
250
  reject(err);
251
  });
252
 
@@ -493,7 +500,7 @@ function getDashboardHTML() {
493
  <body>
494
  <div class="container">
495
  <div class="header">
496
- <h1>🔗 HuggingClip</h1>
497
  <p>Paperclip AI Agent Orchestration on Hugging Face Spaces</p>
498
  </div>
499
 
@@ -675,7 +682,7 @@ function getDashboardHTML() {
675
  // ============================================================================
676
  // Start Server
677
  // ============================================================================
678
- app.listen(PORT, '0.0.0.0', () => {
679
  console.log(`✓ Health server listening on port ${PORT}`);
680
  console.log(`✓ Dashboard: http://localhost:${PORT}/`);
681
  console.log(`✓ API proxy: http://localhost:${PORT}/api/*`);
 
1
+ const express = require("express");
2
+ const cors = require("cors");
3
+ const morgan = require("morgan");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const { promisify } = require("util");
7
+ const http = require("http");
8
 
9
  const app = express();
10
  const PORT = process.env.PORT || 7861;
11
+ const PAPERCLIP_HOST = process.env.HOST || "127.0.0.1";
12
  const PAPERCLIP_PORT = 3100;
13
 
14
  // Middleware
15
  app.use(cors());
16
+ app.use(morgan("combined"));
17
  app.use(express.json());
18
  app.use(express.urlencoded({ extended: true }));
19
 
20
  // ============================================================================
21
  // Health Check Endpoint
22
  // ============================================================================
23
+ app.get("/health", async (req, res) => {
24
  try {
25
  const syncStatus = readSyncStatus();
26
  const now = Math.floor(Date.now() / 1000);
 
30
  const paperclipStatus = await checkPaperclipHealth();
31
 
32
  res.status(200).json({
33
+ status: "healthy",
34
  timestamp: new Date().toISOString(),
35
  uptime: Math.floor(uptime),
36
  services: {
37
  healthServer: {
38
+ status: "running",
39
  port: PORT,
40
+ uptime: Math.floor(uptime),
41
  },
42
  paperclip: {
43
  status: paperclipStatus.status,
44
  port: PAPERCLIP_PORT,
45
+ url: `http://${PAPERCLIP_HOST}:${PAPERCLIP_PORT}`,
46
  },
47
  database: {
48
+ status: syncStatus.db_status || "unknown",
49
  lastSync: syncStatus.last_sync_time || null,
50
+ lastSyncError: syncStatus.last_error || null,
51
+ },
52
  },
53
  backup: {
54
+ enabled: process.env.SYNC_DISABLED !== "true",
55
  interval: process.env.SYNC_INTERVAL || 180,
56
  lastSync: syncStatus.last_sync_time,
57
+ nextSync: syncStatus.last_sync_time
58
+ ? new Date(
59
+ syncStatus.last_sync_time +
60
+ parseInt(process.env.SYNC_INTERVAL || 180) * 1000,
61
+ ).toISOString()
62
+ : null,
63
+ },
64
  });
65
  } catch (error) {
66
  res.status(503).json({
67
+ status: "unhealthy",
68
  error: error.message,
69
+ timestamp: new Date().toISOString(),
70
  });
71
  }
72
  });
 
74
  // ============================================================================
75
  // Dashboard Route
76
  // ============================================================================
77
+ app.get("/", (req, res) => {
78
  res.send(getDashboardHTML());
79
  });
80
 
81
+ app.get("/dashboard/", (req, res) => {
82
  res.send(getDashboardHTML());
83
  });
84
 
85
+ app.get("/dashboard/status", (req, res) => {
86
  const syncStatus = readSyncStatus();
87
  const uptime = process.uptime();
88
 
 
91
  startTime: new Date(Date.now() - uptime * 1000).toISOString(),
92
  syncStatus: syncStatus,
93
  environment: {
94
+ syncDisabled: process.env.SYNC_DISABLED === "true",
95
  syncInterval: process.env.SYNC_INTERVAL || 180,
96
+ paperclipHome: process.env.PAPERCLIP_HOME || "/paperclip",
97
+ },
98
  });
99
  });
100
 
101
  // ============================================================================
102
  // UptimeRobot Setup Route
103
  // ============================================================================
104
+ app.post("/dashboard/uptimerobot/setup", (req, res) => {
105
  const { webhookUrl } = req.body;
106
 
107
  if (!webhookUrl) {
108
+ return res.status(400).json({ error: "webhookUrl required" });
109
  }
110
 
111
  // Store webhook URL in environment or file
 
113
 
114
  res.json({
115
  success: true,
116
+ message: "UptimeRobot webhook configured",
117
+ details: "Health checks will now notify UptimeRobot to prevent sleep",
118
  });
119
  });
120
 
 
123
  // ============================================================================
124
 
125
  // Proxy all /app/* requests to Paperclip
126
+ app.all("/app/*", async (req, res) => {
127
+ const targetPath = req.path.replace("/app", "") || "/";
128
  const targetUrl = `http://${PAPERCLIP_HOST}:${PAPERCLIP_PORT}${targetPath}`;
129
 
130
  try {
 
132
  req.method,
133
  targetUrl,
134
  req.headers,
135
+ req.body,
136
  );
137
 
138
  // Copy response headers
139
+ Object.keys(response.headers).forEach((key) => {
140
  res.setHeader(key, response.headers[key]);
141
  });
142
 
 
144
  } catch (error) {
145
  console.error(`Proxy error: ${error.message}`);
146
  res.status(503).json({
147
+ error: "Paperclip service unavailable",
148
+ details: error.message,
149
  });
150
  }
151
  });
152
 
153
  // Proxy all /api/* requests to Paperclip
154
+ app.all("/api/*", async (req, res) => {
155
  const targetPath = req.path;
156
  const targetUrl = `http://${PAPERCLIP_HOST}:${PAPERCLIP_PORT}${targetPath}`;
157
 
 
160
  req.method,
161
  targetUrl,
162
  req.headers,
163
+ req.body,
164
  );
165
 
166
+ Object.keys(response.headers).forEach((key) => {
167
  res.setHeader(key, response.headers[key]);
168
  });
169
 
 
171
  } catch (error) {
172
  console.error(`API proxy error: ${error.message}`);
173
  res.status(503).json({
174
+ error: "Paperclip API unavailable",
175
+ details: error.message,
176
  });
177
  }
178
  });
 
180
  // ============================================================================
181
  // Default Route - Redirect to Dashboard
182
  // ============================================================================
183
+ app.get("/", (req, res) => {
184
  res.send(getDashboardHTML());
185
  });
186
 
 
190
 
191
  function readSyncStatus() {
192
  try {
193
+ if (fs.existsSync("/tmp/sync-status.json")) {
194
+ const data = fs.readFileSync("/tmp/sync-status.json", "utf8");
195
  return JSON.parse(data);
196
  }
197
  } catch (error) {
198
+ console.error("Error reading sync status:", error.message);
199
  }
200
 
201
  return {
202
+ db_status: "unknown",
203
  last_sync_time: null,
204
  last_error: null,
205
+ sync_count: 0,
206
  };
207
  }
208
 
 
211
  const healthUrl = `http://${PAPERCLIP_HOST}:${PAPERCLIP_PORT}/health`;
212
 
213
  const timeout = setTimeout(() => {
214
+ resolve({ status: "unreachable", reason: "timeout" });
215
  }, 5000);
216
 
217
+ http
218
+ .get(healthUrl, (res) => {
219
+ clearTimeout(timeout);
220
+ resolve({ status: "running", statusCode: res.statusCode });
221
+ })
222
+ .on("error", (err) => {
223
+ clearTimeout(timeout);
224
+ resolve({ status: "unreachable", reason: err.message });
225
+ });
226
  });
227
  }
228
 
 
232
  method,
233
  headers: {
234
  ...headers,
235
+ host: `${PAPERCLIP_HOST}:${PAPERCLIP_PORT}`,
236
  },
237
+ timeout: 30000,
238
  };
239
 
240
  const req = http.request(url, options, (res) => {
241
+ let data = "";
242
 
243
+ res.on("data", (chunk) => {
244
  data += chunk;
245
  });
246
 
247
+ res.on("end", () => {
248
  resolve({
249
  statusCode: res.statusCode,
250
  headers: res.headers,
251
+ body: data,
252
  });
253
  });
254
  });
255
 
256
+ req.on("error", (err) => {
257
  reject(err);
258
  });
259
 
 
500
  <body>
501
  <div class="container">
502
  <div class="header">
503
+ <h1>📎 HuggingClip</h1>
504
  <p>Paperclip AI Agent Orchestration on Hugging Face Spaces</p>
505
  </div>
506
 
 
682
  // ============================================================================
683
  // Start Server
684
  // ============================================================================
685
+ app.listen(PORT, "0.0.0.0", () => {
686
  console.log(`✓ Health server listening on port ${PORT}`);
687
  console.log(`✓ Dashboard: http://localhost:${PORT}/`);
688
  console.log(`✓ API proxy: http://localhost:${PORT}/api/*`);