feat: add sync-server for agent runner git-pull trigger
This commit is contained in:
72
sync-server.js
Normal file
72
sync-server.js
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* sync-server.js — runs inside the Theia container on port 3001
|
||||
* Called by vibn-agent-runner after an agent session auto-commits to Gitea.
|
||||
* Triggers git pull (or clone) so "Open in Theia" immediately shows new files.
|
||||
*
|
||||
* POST /sync { repo: "mark/sportsy" }
|
||||
* GET /health
|
||||
*/
|
||||
const http = require('http');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const WORKSPACE = '/home/project';
|
||||
const GITEA_BASE = process.env.GITEA_API_URL || 'https://git.vibnai.com';
|
||||
const GITEA_TOKEN = process.env.GITEA_TOKEN || process.env.GITEA_API_TOKEN || '';
|
||||
const PORT = 3001;
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
if (req.url === '/health' && req.method === 'GET') {
|
||||
res.writeHead(200).end(JSON.stringify({ ok: true, workspace: WORKSPACE }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.url === '/sync' && req.method === 'POST') {
|
||||
let body = '';
|
||||
req.on('data', d => { body += d; });
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const { repo } = JSON.parse(body || '{}');
|
||||
if (!repo || !/^[\w.-]+\/[\w.-]+$/.test(repo)) {
|
||||
res.writeHead(400).end(JSON.stringify({ error: 'invalid repo' }));
|
||||
return;
|
||||
}
|
||||
|
||||
const repoName = repo.split('/').pop();
|
||||
const cloneDir = `${WORKSPACE}/${repoName}`;
|
||||
const authedUrl = GITEA_BASE.replace('https://', `https://vibn:${GITEA_TOKEN}@`) + `/${repo}.git`;
|
||||
|
||||
let action;
|
||||
try {
|
||||
if (require('fs').existsSync(`${cloneDir}/.git`)) {
|
||||
execSync(`git -C "${cloneDir}" fetch --depth=50 origin main 2>&1`, { timeout: 30000 });
|
||||
execSync(`git -C "${cloneDir}" reset --hard origin/main 2>&1`, { timeout: 10000 });
|
||||
action = 'pulled';
|
||||
} else {
|
||||
execSync(`git clone --depth=50 "${authedUrl}" "${cloneDir}" 2>&1`, { timeout: 60000 });
|
||||
action = 'cloned';
|
||||
}
|
||||
} catch (gitErr) {
|
||||
console.error('[sync-server] git error:', gitErr.message);
|
||||
res.writeHead(500).end(JSON.stringify({ ok: false, error: gitErr.message }));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[sync-server] ${action} ${repo} -> ${cloneDir}`);
|
||||
res.writeHead(200).end(JSON.stringify({ ok: true, action, repo, path: cloneDir }));
|
||||
} catch (err) {
|
||||
console.error('[sync-server] error:', err.message);
|
||||
res.writeHead(500).end(JSON.stringify({ ok: false, error: err.message }));
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeHead(404).end(JSON.stringify({ error: 'not found' }));
|
||||
});
|
||||
|
||||
server.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`[sync-server] Listening on :${PORT} - workspace: ${WORKSPACE}`);
|
||||
});
|
||||
Reference in New Issue
Block a user