#!/usr/bin/env node /** * SessionStart hook: make sure the glance daemon is running, then record the event. * * This is the only hook that spawns anything. It always exits 0 — a monitoring * dashboard must never be the reason a Grok Build session fails to start. */ import fs from "node:fs"; import path from "node:path"; import { spawn } from "node:child_process"; import { PLUGIN_ROOT, baseUrl, envEnvelope, glanceHome, hookHeaders, isDaemonUp, postJson, readConfig, readStdinJson, sleep, } from "./glance-lib.mjs"; const SERVER_ENTRY = path.join(PLUGIN_ROOT, "dist", "server", "index.js"); async function ensureDaemon(cfg) { if (await isDaemonUp(cfg)) return true; if (!fs.existsSync(SERVER_ENTRY)) { // dist/ ships with the plugin, so this means an incomplete checkout. Say so once, on // stderr, where it is recorded but harmless — a hook must never fail a session. process.stderr.write( `[grok-glance] dist/ is missing - run \`npm install && npm run build\` in ${PLUGIN_ROOT}\n`, ); return false; } const home = glanceHome(); fs.mkdirSync(home, { recursive: true }); const logFd = fs.openSync(path.join(home, "daemon.log"), "a"); const child = spawn(process.execPath, [SERVER_ENTRY], { detached: true, stdio: ["ignore", logFd, logFd], env: { ...process.env, GLANCE_STARTED_BY: "hook" }, }); child.unref(); // Give it a moment to bind before the first http hook fires. for (let i = 0; i < 40; i++) { await sleep(200); if (await isDaemonUp(cfg, 300)) return true; } return false; } const payload = envEnvelope(await readStdinJson()); const cfg = readConfig(); try { const up = await ensureDaemon(cfg); if (up) { // hookHeaders() is read here, not at import time: on a first-ever run the daemon we just // spawned is what created hook.secret. await postJson(`${baseUrl(cfg)}/hook/record`, payload, 2500, hookHeaders()); } } catch { // Fail open, always. } process.exit(0);