first commit

This commit is contained in:
iceBear67
2026-08-09 04:00:13 +00:00
commit b3b6bf3f70
46 changed files with 7146 additions and 0 deletions
Executable
+247
View File
@@ -0,0 +1,247 @@
#!/usr/bin/env node
/**
* grok-glance CLI.
*
* Privileged operations (minting enrollment codes, revoking devices) are authenticated with
* a local admin token read from $GLANCE_HOME/admin.token, not by "is this request from
* localhost". That distinction matters: `tailscale serve` proxies remote traffic to
* 127.0.0.1, so the daemon cannot tell a local caller from a tunnelled one by address alone.
*/
import fs from "node:fs";
import path from "node:path";
import { spawn } from "node:child_process";
import {
PLUGIN_ROOT,
baseUrl,
glanceHome,
isDaemonUp,
readConfig,
sleep,
} from "./glance-lib.mjs";
const SERVER_ENTRY = path.join(PLUGIN_ROOT, "dist", "server", "index.js");
const cfg = readConfig();
const cmd = process.argv[2] ?? "status";
const args = process.argv.slice(3);
function adminToken() {
try {
return fs.readFileSync(path.join(glanceHome(), "admin.token"), "utf8").trim();
} catch {
return null;
}
}
async function api(pathname, { method = "GET", body, admin = false } = {}) {
const headers = { "content-type": "application/json" };
if (admin) {
const token = adminToken();
if (!token) {
throw new Error("no admin token found - is the daemon running? try `glance up`");
}
headers["x-glance-admin"] = token;
}
const res = await fetch(`${baseUrl(cfg)}${pathname}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(10_000),
});
const text = await res.text();
let data = null;
try {
data = text ? JSON.parse(text) : null;
} catch {
data = null;
}
if (!res.ok) throw new Error(data?.error ?? `${res.status} ${res.statusText}`);
return data;
}
function requireBuild() {
if (!fs.existsSync(SERVER_ENTRY)) {
console.error(`grok-glance is not built yet.\n\n cd ${PLUGIN_ROOT}\n npm install && npm run build\n`);
process.exit(1);
}
}
async function ensureUp() {
if (await isDaemonUp(cfg)) return true;
requireBuild();
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],
});
child.unref();
for (let i = 0; i < 40; i++) {
await sleep(200);
if (await isDaemonUp(cfg, 300)) return true;
}
console.error(`daemon did not come up; see ${path.join(home, "daemon.log")}`);
return false;
}
switch (cmd) {
case "serve":
case "start": {
requireBuild();
await import(SERVER_ENTRY);
break;
}
case "up": {
if (await ensureUp()) console.log(`grok-glance running on ${baseUrl(cfg)}`);
else process.exit(1);
break;
}
case "stop": {
if (!(await isDaemonUp(cfg))) {
console.log("not running");
break;
}
await api("/local/shutdown", { method: "POST", admin: true });
console.log("stopped");
break;
}
case "status": {
if (!(await isDaemonUp(cfg))) {
console.log(`grok-glance: not running (port ${cfg.port})`);
console.log("start it with: glance up");
break;
}
const s = await api("/local/status", { admin: true });
console.log(`grok-glance ${s.version} on ${baseUrl(cfg)}`);
console.log(` public origin : ${s.origin ?? "(not configured - see README)"}`);
console.log(` rp id : ${s.rpId ?? "(not configured)"}`);
console.log(` devices : ${s.devices}`);
console.log(` approval mode : ${s.approval.mode}`);
console.log(` watchers : ${s.watchers}`);
console.log(` sessions : ${s.sessions}`);
console.log(` events kept : ${s.events}`);
if (s.devices === 0) console.log("\nNo device enrolled yet. Run: glance enroll");
break;
}
case "enroll": {
if (!(await ensureUp())) process.exit(1);
const out = await api("/local/enroll", { method: "POST", admin: true });
console.log("\n Open this on your phone:\n");
console.log(` ${out.url}\n`);
console.log(` Enrollment code: ${out.code}`);
console.log(` Valid for: ${Math.round(out.expiresInMs / 60000)} minutes (single use)\n`);
if (!out.originConfigured) {
console.log(" Note: no public origin configured yet, so the URL above is localhost.");
console.log(" Set one up first (see README), e.g.:\n");
console.log(" tailscale serve --bg 127.0.0.1:" + cfg.port);
console.log(" glance set-origin https://<your-box>.<tailnet>.ts.net\n");
}
break;
}
case "set-origin": {
const origin = args[0];
if (!origin) {
console.error("usage: glance set-origin https://your-box.tailnet.ts.net");
process.exit(1);
}
if (!(await ensureUp())) process.exit(1);
const out = await api("/local/origin", { method: "POST", admin: true, body: { origin } });
console.log(`origin : ${out.origin}`);
console.log(`rp id : ${out.rpId}`);
console.log("\nEnrolled devices are bound to the rp id. Changing it invalidates them.");
break;
}
case "devices": {
if (!(await isDaemonUp(cfg))) {
console.error("not running");
process.exit(1);
}
const out = await api("/local/devices", { admin: true });
if (!out.devices.length) {
console.log("no devices enrolled - run: glance enroll");
break;
}
for (const d of out.devices) {
console.log(`${d.id.slice(0, 16)}… ${d.label.padEnd(24)} added ${new Date(d.createdAt).toISOString().slice(0, 10)} last seen ${d.lastUsedAt ? new Date(d.lastUsedAt).toISOString().slice(0, 16).replace("T", " ") : "never"}`);
}
break;
}
case "revoke": {
if (!args[0]) {
console.error("usage: glance revoke <device-id-prefix>");
process.exit(1);
}
const out = await api("/local/devices/revoke", {
method: "POST",
admin: true,
body: { idPrefix: args[0] },
});
console.log(`revoked ${out.revoked} device(s)`);
break;
}
case "approval": {
const mode = args[0];
if (!["off", "risky", "all"].includes(mode)) {
console.error("usage: glance approval <off|risky|all>");
process.exit(1);
}
const out = await api("/local/approval", { method: "POST", admin: true, body: { mode } });
console.log(`approval mode: ${out.mode}`);
break;
}
case "sync-hooks": {
const file = path.join(PLUGIN_ROOT, "hooks", "hooks.json");
const doc = JSON.parse(fs.readFileSync(file, "utf8"));
let changed = 0;
for (const groups of Object.values(doc.hooks ?? {})) {
for (const group of groups) {
for (const h of group.hooks ?? []) {
if (h.type === "http" && typeof h.url === "string") {
const next = h.url.replace(/127\.0\.0\.1:\d+/, `127.0.0.1:${cfg.port}`);
if (next !== h.url) changed++;
h.url = next;
}
}
}
}
fs.writeFileSync(file, JSON.stringify(doc, null, 2) + "\n");
console.log(`rewrote ${changed} hook url(s) to port ${cfg.port}`);
break;
}
case "logs": {
const file = path.join(glanceHome(), "daemon.log");
if (!fs.existsSync(file)) {
console.log("no log yet");
break;
}
process.stdout.write(fs.readFileSync(file, "utf8").split("\n").slice(-60).join("\n") + "\n");
break;
}
default:
console.log(`grok-glance - glance at Grok Build from your phone
glance up start the daemon in the background
glance serve run it in the foreground
glance stop stop it
glance status show what is running
glance enroll mint a one-time code to enrol a phone
glance set-origin <url> set the public https origin (and webauthn rp id)
glance devices list enrolled devices
glance revoke <id-prefix> revoke a device
glance approval <off|risky|all> remote approval policy
glance sync-hooks rewrite hook urls after a port change
glance logs tail the daemon log
`);
}