first commit
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Shared helpers for the grok-glance hook scripts and CLI.
|
||||
*
|
||||
* Deliberately dependency-free and stdlib-only: these run on the critical path of
|
||||
* every Grok Build tool call, so they must start fast and never wedge a session.
|
||||
*/
|
||||
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import os from "node:os";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export const PLUGIN_ROOT = path.resolve(fileURLToPath(import.meta.url), "../..");
|
||||
|
||||
export const DEFAULT_PORT = 8791;
|
||||
|
||||
/**
|
||||
* State lives in one fixed place so that hooks (which get GROK_PLUGIN_DATA) and the
|
||||
* CLI (which does not) always agree on where config, credentials and events are.
|
||||
*/
|
||||
export function glanceHome() {
|
||||
if (process.env.GLANCE_HOME) return path.resolve(process.env.GLANCE_HOME);
|
||||
return path.join(os.homedir(), ".grok", "glance");
|
||||
}
|
||||
|
||||
export function readConfig() {
|
||||
const file = path.join(glanceHome(), "config.json");
|
||||
let raw = {};
|
||||
try {
|
||||
raw = JSON.parse(fs.readFileSync(file, "utf8"));
|
||||
} catch {
|
||||
// No config yet, or unreadable: defaults are always usable.
|
||||
}
|
||||
const port = Number(process.env.GLANCE_PORT ?? raw.port ?? DEFAULT_PORT);
|
||||
return {
|
||||
...raw,
|
||||
port: Number.isFinite(port) ? port : DEFAULT_PORT,
|
||||
host: raw.host ?? "127.0.0.1",
|
||||
};
|
||||
}
|
||||
|
||||
export function baseUrl(cfg = readConfig()) {
|
||||
return `http://127.0.0.1:${cfg.port}`;
|
||||
}
|
||||
|
||||
/** Read the hook payload that Grok Build writes to stdin. Returns {} if there is none. */
|
||||
export async function readStdinJson() {
|
||||
if (process.stdin.isTTY) return {};
|
||||
const chunks = [];
|
||||
try {
|
||||
for await (const chunk of process.stdin) chunks.push(chunk);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
const text = Buffer.concat(chunks).toString("utf8").trim();
|
||||
if (!text) return {};
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grok Build also passes the event in the environment. We merge it in so a payload that
|
||||
* is missing fields (or absent entirely) still produces a usable event.
|
||||
*/
|
||||
export function envEnvelope(payload) {
|
||||
return {
|
||||
hookEventName: payload.hookEventName ?? process.env.GROK_HOOK_EVENT ?? "Unknown",
|
||||
sessionId: payload.sessionId ?? process.env.GROK_SESSION_ID ?? "unknown",
|
||||
workspaceRoot:
|
||||
payload.workspaceRoot ?? process.env.GROK_WORKSPACE_ROOT ?? payload.cwd ?? process.cwd(),
|
||||
cwd: payload.cwd ?? process.cwd(),
|
||||
hookName: process.env.GROK_HOOK_NAME ?? undefined,
|
||||
...payload,
|
||||
};
|
||||
}
|
||||
|
||||
export async function postJson(url, body, timeoutMs) {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
signal: AbortSignal.timeout(timeoutMs),
|
||||
});
|
||||
const text = await res.text();
|
||||
if (!text) return { status: res.status, data: null };
|
||||
try {
|
||||
return { status: res.status, data: JSON.parse(text) };
|
||||
} catch {
|
||||
return { status: res.status, data: null };
|
||||
}
|
||||
}
|
||||
|
||||
export async function isDaemonUp(cfg = readConfig(), timeoutMs = 400) {
|
||||
try {
|
||||
const res = await fetch(`${baseUrl(cfg)}/healthz`, {
|
||||
signal: AbortSignal.timeout(timeoutMs),
|
||||
});
|
||||
return res.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
Reference in New Issue
Block a user