Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357#!/usr/bin/env node/** * 🤖 ac-tezbot - Tezos Operations Daemon * * A simple background daemon that handles Tezos RPC calls * without blocking the main TUI. Uses file-based IPC. * * Communication: * Command: /tmp/tezbot-cmd.json (write command here) * Response: /tmp/tezbot-out.json (read result here) * Status: /tmp/tezbot.pid (daemon PID) * * Usage: * node ac-tezbot.mjs - Start daemon * node ac-tezbot.mjs stop - Stop daemon * node ac-tezbot.mjs status - Check if running * node ac-tezbot.mjs cmd balance - Send command directly */
import fs from 'fs';import path from 'path';import { fileURLToPath } from 'url';import { spawn, execSync } from 'child_process';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// File paths for IPCconst PATHS = { pid: '/tmp/tezbot.pid', cmd: '/tmp/tezbot-cmd.json', out: '/tmp/tezbot-out.json', log: '/tmp/tezbot.log', keepsScript: path.join(__dirname, 'keeps.mjs')};
// ============================================================================// Daemon Mode// ============================================================================
async function runDaemon() { console.log('🤖 ac-tezbot starting...'); // Write PID file fs.writeFileSync(PATHS.pid, process.pid.toString()); // Clean up old files if (fs.existsSync(PATHS.cmd)) fs.unlinkSync(PATHS.cmd); if (fs.existsSync(PATHS.out)) fs.unlinkSync(PATHS.out); // Write initial status writeOutput({ status: 'ready', message: 'Tezbot ready', timestamp: Date.now() }); console.log(`📁 Watching: ${PATHS.cmd}`); console.log(`📤 Output: ${PATHS.out}`); console.log(`🆔 PID: ${process.pid}`); // Watch for command file changes let processing = false; let lastMtime = 0; // Poll for commands (more reliable than fs.watch in containers) setInterval(async () => { if (processing) return; try { if (!fs.existsSync(PATHS.cmd)) return; const stat = fs.statSync(PATHS.cmd); if (stat.mtimeMs <= lastMtime) return; lastMtime = stat.mtimeMs; processing = true; const cmdData = JSON.parse(fs.readFileSync(PATHS.cmd, 'utf8')); console.log(`\n📥 Command: ${cmdData.action}`); await processCommand(cmdData); // Remove command file after processing fs.unlinkSync(PATHS.cmd); processing = false; } catch (err) { console.error('❌ Error:', err.message); writeOutput({ status: 'error', error: err.message, timestamp: Date.now() }); processing = false; } }, 200); // Poll every 200ms // Handle shutdown process.on('SIGTERM', cleanup); process.on('SIGINT', cleanup); console.log('✅ Tezbot running. Ctrl+C to stop.\n');}
function cleanup() { console.log('\n🛑 Tezbot stopping...'); if (fs.existsSync(PATHS.pid)) fs.unlinkSync(PATHS.pid); process.exit(0);}
// ============================================================================// Command Processing// ============================================================================
async function processCommand(cmd) { const { action, args = [], network = 'ghostnet', timeout = 30000 } = cmd; writeOutput({ status: 'running', action, message: `Executing: ${action}...`, timestamp: Date.now() }); return new Promise((resolve) => { const fullArgs = [PATHS.keepsScript, action, ...args]; let output = ''; let resolved = false; const child = spawn('node', fullArgs, { cwd: path.dirname(PATHS.keepsScript), env: { ...process.env, TEZBOT_NETWORK: network }, stdio: ['ignore', 'pipe', 'pipe'] }); child.stdout.on('data', (data) => { output += data.toString(); }); child.stderr.on('data', (data) => { output += data.toString(); }); child.on('close', (code) => { if (resolved) return; resolved = true; const result = { status: code === 0 ? 'success' : 'error', action, code, output: output.trim(), timestamp: Date.now() }; // Parse structured data from output if possible if (action === 'balance' && code === 0) { const match = output.match(/Balance:\s*([\d.]+)\s*XTZ/); if (match) result.balance = parseFloat(match[1]); } writeOutput(result); console.log(`✅ ${action} completed (code: ${code})`); resolve(result); }); child.on('error', (err) => { if (resolved) return; resolved = true; const result = { status: 'error', action, error: err.message, timestamp: Date.now() }; writeOutput(result); console.log(`❌ ${action} failed: ${err.message}`); resolve(result); }); // Timeout setTimeout(() => { if (!resolved) { resolved = true; try { child.kill('SIGKILL'); } catch (e) {} const result = { status: 'timeout', action, error: `Operation timed out after ${timeout/1000}s`, output: output.trim(), timestamp: Date.now() }; writeOutput(result); console.log(`⏰ ${action} timed out`); resolve(result); } }, timeout); });}
function writeOutput(data) { fs.writeFileSync(PATHS.out, JSON.stringify(data, null, 2));}
// ============================================================================// Client Commands// ============================================================================
function sendCommand(action, args = []) { const cmd = { action, args, timestamp: Date.now() }; fs.writeFileSync(PATHS.cmd, JSON.stringify(cmd, null, 2)); console.log(`📤 Sent: ${action}`); // Wait for response const startTime = Date.now(); const timeout = 35000; process.stdout.write('⏳ Waiting'); const poll = setInterval(() => { process.stdout.write('.'); if (Date.now() - startTime > timeout) { clearInterval(poll); console.log('\n❌ Timeout waiting for response'); process.exit(1); } if (!fs.existsSync(PATHS.out)) return; try { const result = JSON.parse(fs.readFileSync(PATHS.out, 'utf8')); // Wait for final status (not 'running' or 'ready') const finalStatuses = ['success', 'error', 'timeout']; if (result.timestamp > cmd.timestamp && result.action === action && finalStatuses.includes(result.status)) { clearInterval(poll); console.log('\n'); if (result.output) { console.log(result.output); } else { console.log(JSON.stringify(result, null, 2)); } process.exit(result.status === 'success' ? 0 : 1); } } catch (e) {} }, 300);}
function checkStatus() { if (!fs.existsSync(PATHS.pid)) { console.log('🔴 Tezbot not running'); return false; } const pid = fs.readFileSync(PATHS.pid, 'utf8').trim(); try { process.kill(parseInt(pid), 0); // Check if process exists console.log(`🟢 Tezbot running (PID: ${pid})`); if (fs.existsSync(PATHS.out)) { const lastOutput = JSON.parse(fs.readFileSync(PATHS.out, 'utf8')); const age = Math.round((Date.now() - lastOutput.timestamp) / 1000); console.log(` Last activity: ${age}s ago (${lastOutput.status})`); } return true; } catch (e) { console.log('🔴 Tezbot not running (stale PID file)'); fs.unlinkSync(PATHS.pid); return false; }}
function stopDaemon() { if (!fs.existsSync(PATHS.pid)) { console.log('🔴 Tezbot not running'); return; } const pid = fs.readFileSync(PATHS.pid, 'utf8').trim(); try { process.kill(parseInt(pid), 'SIGTERM'); console.log(`🛑 Sent SIGTERM to PID ${pid}`); // Wait a moment and clean up setTimeout(() => { if (fs.existsSync(PATHS.pid)) fs.unlinkSync(PATHS.pid); console.log('✅ Tezbot stopped'); }, 500); } catch (e) { console.log('🔴 Could not stop tezbot:', e.message); if (fs.existsSync(PATHS.pid)) fs.unlinkSync(PATHS.pid); }}
// ============================================================================// Main// ============================================================================
const args = process.argv.slice(2);
switch (args[0]) { case 'stop': stopDaemon(); break; case 'status': checkStatus(); break; case 'cmd': if (!args[1]) { console.log('Usage: node ac-tezbot.mjs cmd <action> [args...]'); console.log('Actions: balance, status, tokens, mint, upload, lock'); process.exit(1); } if (!checkStatus()) { console.log('⚠️ Start tezbot first: node ac-tezbot.mjs'); process.exit(1); } sendCommand(args[1], args.slice(2)); break; case undefined: case 'start': if (checkStatus()) { console.log('⚠️ Already running. Use "stop" first to restart.'); process.exit(1); } runDaemon(); break; default: console.log(`🤖 ac-tezbot - Tezos Operations Daemon
Usage: node ac-tezbot.mjs Start daemon node ac-tezbot.mjs stop Stop daemon node ac-tezbot.mjs status Check status node ac-tezbot.mjs cmd <action> Send command
Actions: balance Check wallet balance status Contract status tokens List tokens mint Mint token (needs piece name) upload Upload to IPFS lock Lock token metadata`);}