Grok Build loads plugins out of ~/.grok/plugins/ and clones marketplace sources straight from git; it never runs npm install or a build for you. So a plugin that ships TypeScript is a plugin you have to build by hand before it does anything, and the SessionStart hook's first act is to print "not built yet". dist/ is now committed, and the tree is arranged so that a bare clone can run: - The daemon is bundled to a single ESM file with rolldown, platform node. Its one runtime dependency (@simplewebauthn/server, plus the asn1/cbor tree under it) is inlined; the only imports left in the output are node: builtins. Not minified — a committed blob nobody can read is worse than no committed blob. - tsc no longer emits for the server, it only typechecks (noEmit). rolldown emits. - dist/web was already a self-contained static bundle. - The hook scripts under bin/ were stdlib-only from the start. .grok-plugin/marketplace.json makes the repo its own one-entry catalog with a local source of "./", so `grok plugin marketplace add <git-url>` followed by `grok plugin install grok-glance` works without pinning a SHA of itself. `npm run check:dist` rebuilds and fails if the committed output is stale — the one real hazard of checking in build output. Also drops the daemon's "non-default port, run `glance sync-hooks`" startup note, which the previous commit should have taken with the rest of that scheme; the hook scripts read config.json themselves, so a non-default port needs nothing. The e2e suite now takes GLANCE_ROOT and was run twice: once against the repo, once against a copy containing only tracked files plus dist/ and no node_modules — which is what actually demonstrates the claim, passkey registration and assertion included. 233 checks, both runs green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
73 lines
2.0 KiB
JavaScript
Executable File
73 lines
2.0 KiB
JavaScript
Executable File
#!/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);
|