[object Object]

← back to Ticket System

Sync YOLO cycles into the ticket ledger

7ebd636ec83a65ccc1c4f7a75d35dc2df4171151 · 2026-08-30 23:07:41 -0700 · Steve Abrams

Files touched

Diff

commit 7ebd636ec83a65ccc1c4f7a75d35dc2df4171151
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Aug 30 23:07:41 2026 -0700

    Sync YOLO cycles into the ticket ledger
---
 .gitignore                       |  3 ++
 com.steve.yolo-ticket-sync.plist | 28 +++++++++++++++
 scripts/yolo-ticket-sync.mjs     | 75 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 106 insertions(+)

diff --git a/.gitignore b/.gitignore
index 1924158c..3608ca56 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,7 @@
 node_modules/
+data/yolo-ticket-sync.json
+yolo-ticket-sync.out.log
+yolo-ticket-sync.err.log
 .env*
 tmp/
 *.log
diff --git a/com.steve.yolo-ticket-sync.plist b/com.steve.yolo-ticket-sync.plist
new file mode 100644
index 00000000..1facc787
--- /dev/null
+++ b/com.steve.yolo-ticket-sync.plist
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>Label</key>
+  <string>com.steve.yolo-ticket-sync</string>
+  <key>ProgramArguments</key>
+  <array>
+    <string>/opt/homebrew/bin/node</string>
+    <string>/Users/macstudio3/Projects/ticket-system/scripts/yolo-ticket-sync.mjs</string>
+  </array>
+  <key>RunAtLoad</key>
+  <true/>
+  <key>StartInterval</key>
+  <integer>120</integer>
+  <key>EnvironmentVariables</key>
+  <dict>
+    <key>HOME</key>
+    <string>/Users/macstudio3</string>
+    <key>PATH</key>
+    <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
+  </dict>
+  <key>StandardOutPath</key>
+  <string>/Users/macstudio3/Projects/ticket-system/yolo-ticket-sync.out.log</string>
+  <key>StandardErrorPath</key>
+  <string>/Users/macstudio3/Projects/ticket-system/yolo-ticket-sync.err.log</string>
+</dict>
+</plist>
diff --git a/scripts/yolo-ticket-sync.mjs b/scripts/yolo-ticket-sync.mjs
new file mode 100644
index 00000000..eaa37869
--- /dev/null
+++ b/scripts/yolo-ticket-sync.mjs
@@ -0,0 +1,75 @@
+#!/usr/bin/env node
+
+import { execFileSync } from 'node:child_process';
+import fs from 'node:fs';
+import path from 'node:path';
+
+const TICKET = 'TK-10986';
+const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
+const STATE_FILE = path.join(ROOT, 'data', 'yolo-ticket-sync.json');
+const TK = '/opt/homebrew/bin/tk';
+const SSH = '/usr/bin/ssh';
+const HOST = 'root@45.61.58.125';
+const REMOTE_STATUS = `YOLO_PASS=$(pm2 jlist | jq -r '.[]|select(.name=="yolo-agent").pm2_env.YOLO_AUTH_PASS'); curl -fsS -u "admin:$YOLO_PASS" http://127.0.0.1:9670/api/status`;
+const REMOTE_START = `YOLO_PASS=$(pm2 jlist | jq -r '.[]|select(.name=="yolo-agent").pm2_env.YOLO_AUTH_PASS'); curl -fsS -u "admin:$YOLO_PASS" -X POST http://127.0.0.1:9670/api/start`;
+
+function ssh(command) {
+  return execFileSync(SSH, ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', HOST, command], {
+    encoding: 'utf8', timeout: 20_000, maxBuffer: 4 * 1024 * 1024,
+  });
+}
+
+function tk(kind, text) {
+  execFileSync(TK, [kind, TICKET, text, '-a', 'yolo-ticket-sync'], {
+    encoding: 'utf8', timeout: 10_000,
+  });
+}
+
+function loadState() {
+  try { return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8')); }
+  catch { return { lastTaskId: null, lastRunning: null, lastError: null }; }
+}
+
+function saveState(state) {
+  fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
+  fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2) + '\n');
+}
+
+const state = loadState();
+
+try {
+  const status = JSON.parse(ssh(REMOTE_STATUS));
+  const result = status.lastResult || null;
+
+  if (status.running === false) {
+    const response = JSON.parse(ssh(REMOTE_START));
+    tk('challenge', `YOLO loop was stopped; monitor issued the existing /api/start recovery (${response.msg || 'started'}). No email; $0 spend.`);
+  }
+
+  if (result?.taskId && result.taskId !== state.lastTaskId) {
+    const summary = String(result.stdout || '').replace(/\s+/g, ' ').trim().slice(0, 700);
+    const railProof = String(result.stderr || '').includes('No Shopify write')
+      ? ' Hard-rail attestation present.' : '';
+    const text = `YOLO cycle #${result.taskId} ${result.status} in ${result.duration || 'unknown duration'}. ${summary || 'No summary returned.'}${railProof} Loop running=${status.running}; mode=${status.mode}; cooldown=${status.cooldownSec}s. No email notification path; spend ceiling <=$5.`;
+    tk(result.status === 'success' ? 'win' : 'challenge', text);
+    state.lastTaskId = result.taskId;
+  }
+
+  if (state.lastRunning !== status.running) {
+    tk('note', `YOLO runtime state changed to running=${status.running}, paused=${status.paused}, mode=${status.mode}, currentTask=${status.currentTask || 'none'}.`);
+    state.lastRunning = status.running;
+  }
+
+  state.lastError = null;
+  state.checkedAt = new Date().toISOString();
+  saveState(state);
+} catch (error) {
+  const message = String(error?.message || error).replace(/\s+/g, ' ').slice(0, 500);
+  if (message !== state.lastError) {
+    try { tk('challenge', `YOLO ticket-sync monitor could not verify the remote loop: ${message}`); } catch {}
+    state.lastError = message;
+  }
+  state.checkedAt = new Date().toISOString();
+  saveState(state);
+  process.exitCode = 1;
+}

← 5ef46940 use owner-safe PM2 CLI wrapper  ·  back to Ticket System  ·  record Kamatera PM2 fracture prevention rollout 32dadef2 →