Ship prebuilt, so a git URL is the whole install

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>
This commit is contained in:
iceBear67andClaude Opus 5 committed 2026-08-09 07:32:49 +00:00
1 parent d20eb9255c
commit 3cfa011d0c
18 files changed
+23916 -55

No files matched your search

-1
View File
@@ -1,4 +1,3 @@
node_modules/
dist/
*.log
.DS_Store
+22
View File
@@ -0,0 +1,22 @@
{
"name": "grok-glance",
"description": "One-entry marketplace: this repo is both the catalog and the plugin, so `grok plugin marketplace add <git-url>` is enough to install it.",
"owner": {
"name": "grok-glance"
},
"plugins": [
{
"name": "grok-glance",
"description": "A passkey-guarded web dashboard that lets you glance at what Grok Build is doing from your phone, and approve or deny risky tool calls remotely.",
"category": "monitoring",
"keywords": [
"grok-glance",
"glance dashboard",
"webauthn passkey",
"remote approval",
"session monitor"
],
"source": { "type": "local", "path": "./" }
}
]
}
+43 -29
View File
@@ -37,49 +37,63 @@ or drive a session.
## Requirements
- Node.js 20 or newer, and npm.
- Node.js 20 or newer. **npm is only needed to develop it** — `dist/` is committed, and the
daemon bundle carries its one runtime dependency inside it, so an installed copy never runs
a build or an install step.
- Grok Build.
- For phone access: [Tailscale](https://tailscale.com/) on both the machine and the phone. See
[Why Tailscale](#why-tailscale-and-not-just-the-lan-ip) — a LAN IP genuinely cannot work.
## Install
Grok Build loads plugins straight out of `~/.grok/plugins/`, so the shortest install is a clone:
```sh
git clone <this repo> grok-glance
cd grok-glance
npm install && npm run build
git clone <this repo> ~/.grok/plugins/grok-glance
```
The build produces `dist/server` (the daemon) and `dist/web` (the dashboard). Both are required;
the daemon serves the dashboard itself. `hooks/hooks.json` is checked in as-is — nothing about it
is generated or machine-specific. The shared secret the hook scripts authenticate with lives in
`~/.grok/glance/hook.secret` (mode 0600) and is created by the daemon on first start; it never
appears in `hooks.json`.
That is the whole thing — no `npm install`, no build. Open `/plugins` in Grok Build and enable
**grok-glance**. On the next session start its `SessionStart` hook boots the daemon in the
background, and the dashboard is on `http://127.0.0.1:8791`.
Then register the directory with Grok Build. Plugins are installed from a marketplace catalog, so
for a local checkout the shortest path is a one-entry catalog. Create
`.grok-plugin/marketplace.json` in a directory that contains your checkout:
### …or from a marketplace, by URL
```json
{
"name": "local",
"description": "Local plugins",
"owner": { "name": "me" },
"plugins": [
{
"name": "grok-glance",
"description": "Passkey-guarded phone dashboard for Grok Build.",
"category": "monitoring",
"source": { "type": "local", "path": "./grok-glance" }
}
]
}
If you would rather install it the way marketplace plugins are installed — or share it with other
machines — this repo is also its own one-entry marketplace (`.grok-plugin/marketplace.json`). Add
it as a marketplace source and install from it:
```sh
grok plugin marketplace add https://your-git-host/you/grok-glance.git
grok plugin install grok-glance --trust
```
…then add that marketplace and install `grok-glance` from Grok Build's `/plugin` interface.
Marketplace sources also live in `~/.grok/config.toml` under `[[marketplace.sources]]` and in
`~/.grok/plugins/known_marketplaces.json`, if you prefer to write them there directly. The TUI's
Marketplace tab reads the same list.
Once installed, the daemon starts by itself: the `SessionStart` hook boots it in the background on
the first session after installation.
### Why there is no build step
`dist/` is checked in:
- `dist/server/index.js` — the daemon, bundled to a single dependency-free ESM file. Its only
runtime dependency, `@simplewebauthn/server`, is inlined; everything else it uses is the Node
standard library. It is *not* minified, so what ships is what you can read.
- `dist/web/` — the dashboard, already a static bundle, which the daemon serves itself.
The hook scripts under `bin/` were stdlib-only from the start. So a clone has nothing to resolve
and nothing to compile, which is what makes a bare git URL enough.
Working on it instead? Then you do need the toolchain:
```sh
npm install
npm run build # tsc typechecks, rolldown bundles the server, vite builds the web app
npm run check:dist # rebuilds and fails if the committed dist/ is stale
```
`hooks/hooks.json` is checked in as-is — nothing about it is generated or machine-specific. The
shared secret the hook scripts authenticate with lives in `~/.grok/glance/hook.secret` (mode 0600)
and is created by the daemon on first start; it never appears in `hooks.json`.
## Get it onto your phone
+5 -1
View File
@@ -77,7 +77,11 @@ function sessionBreakdown(states) {
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`);
console.error(
`grok-glance: ${SERVER_ENTRY} is missing.\n\n` +
`dist/ ships with the plugin, so this checkout is incomplete. Rebuild it:\n\n` +
` cd ${PLUGIN_ROOT}\n npm install && npm run build\n`,
);
process.exit(1);
}
}
+3 -2
View File
@@ -28,9 +28,10 @@ async function ensureDaemon(cfg) {
if (await isDaemonUp(cfg)) return true;
if (!fs.existsSync(SERVER_ENTRY)) {
// Not built yet. Say so once, on stderr, where it is recorded but harmless.
// 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] not built yet - run \`npm install && npm run build\` in ${PLUGIN_ROOT}\n`,
`[grok-glance] dist/ is missing - run \`npm install && npm run build\` in ${PLUGIN_ROOT}\n`,
);
return false;
}
+3 -2
View File
@@ -12,8 +12,9 @@ If no argument was given, treat it as `status`.
Then:
1. Run `node "$GROK_PLUGIN_ROOT/bin/glance" $ARGUMENTS`.
2. If it says the plugin is not built, run `npm install && npm run build` in `$GROK_PLUGIN_ROOT`
(this takes a minute or two) and try again.
2. The plugin ships prebuilt, so this should just work. If it does say the plugin is not built,
`dist/` is missing from the checkout: run `npm install && npm run build` in
`$GROK_PLUGIN_ROOT` (a minute or two) and try again.
3. Report what came back. For `enroll`, show the URL and the code verbatim — the user needs to
type them on their phone, so do not paraphrase or reformat them.
4. If the output mentions that no public origin is configured, explain the Tailscale Serve setup:
+23758
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -0,0 +1,2 @@
/*! tailwindcss v4.3.1 | MIT License | https://tailwindcss.com */
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-outline-style:solid;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-content:"";--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-space-y-reverse:0;--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-lime-400:oklch(84.1% .238 128.85);--color-lime-500:oklch(76.8% .233 130.85);--color-lime-600:oklch(64.8% .2 131.684);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-cyan-400:oklch(78.9% .154 211.53);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-sky-400:oklch(74.6% .16 232.661);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-violet-400:oklch(70.2% .183 293.541);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-600:oklch(54.1% .281 293.009);--color-fuchsia-400:oklch(74% .238 322.16);--color-fuchsia-500:oklch(66.7% .295 322.15);--color-fuchsia-600:oklch(59.1% .293 322.896);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--leading-snug:1.375;--leading-relaxed:1.625;--radius-xl:calc(var(--radius) * 1.5);--radius-2xl:calc(var(--radius) * 2);--radius-3xl:calc(var(--radius) * 3);--ease-out:cubic-bezier(0, 0, .2, 1);--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--blur-md:12px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--shadow-overlay:var(--overlay-shadow);--border-width-field:var(--field-border-width,var(--border-width));--ease-smooth:ease;--ease-out-quad:cubic-bezier(.25, .46, .45, .94);--ease-out-quart:cubic-bezier(.165, .84, .44, 1);--ease-out-fluid:cubic-bezier(.32, .72, 0, 1);--ease-linear:linear}@layer theme{@layer base{:root,.light,.default,[data-theme=light],[data-theme=default]{--lightniLine truncated
+15
View File
@@ -0,0 +1,15 @@
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var l=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var ee=Array.isArray;function S(){}var C={H:null,A:null,T:null,S:null},w=Object.prototype.hasOwnProperty;function te(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function ne(e,t){return te(e.type,t,e.props)}function T(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function re(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var ie=/\/+/g;function ae(e,t){return typeof e==`object`&&e&&e.key!=null?re(``+e.key):t.toString(36)}function E(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(S,S):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function D(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,D(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+ae(e,0):a,ee(o)?(i=``,c!=null&&(i=c.replace(ie,`$&/`)+`/`),D(o,r,i,``,function(e){return e})):o!=null&&(T(o)&&(o=ne(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(ie,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(ee(e))for(var u=0;u<e.length;u++)a=e[u],s=l+ae(a,u),c+=D(a,r,i,s,o);else if(u=m(e),typeof u==`function`)for(e=u.call(e),u=0;!(a=e.next()).done;)a=a.value,s=l+ae(a,u++),c+=D(a,r,i,s,o);else if(s===`object`){if(typeof e.then==`function`)return D(E(e),r,i,a,o);throw r=String(e),Error(`Objects are not valid as a React child (found: `+(r===`[object Object]`?`object with keys {`+Object.keys(e).join(`, `)+`}`:r)+`). If you meant to render a collection of children, use an array instead.`)}return c}function oe(e,t,n){if(e==null)return e;var r=[],i=0;return D(e,r,``,``,function(e){return t.call(n,e,i++)}),r}function se(e){if(e._status===-1){var t=e._result;t=t(),t.then(function(t){(e._status===0||e._status===-1)&&(e._status=1,e._result=t)},function(t){(e._status===0||e._status===-1)&&(e._status=2,e._result=t)}),e._status===-1&&(e._status=0,e._result=t)}if(e._status===1)return e._result.default;throw e._result}var O=typeof reportError==`function`?reportError:function(e){if(typeof window==`o Line truncated
at`)?` (<anonymous>)`:-1<e.stack.indexOf(`@`)?`@unknown:0:0`:``}return`
`+ve+e+ye}var xe=!1;function Se(e,t){if(!e||xe)return``;xe=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var r={DetermineComponentFrameRoot:function(){try{if(t){var n=function(){throw Error()};if(Object.defineProperty(n.prototype,"props",{set:function(){throw Error()}}),typeof Reflect==`object`&&Reflect.construct){try{Reflect.construct(n,[])}catch(e){var r=e}Reflect.construct(e,[],n)}else{try{n.call()}catch(e){r=e}e.call(n.prototype)}}else{try{throw Error()}catch(e){r=e}(n=e())&&typeof n.catch==`function`&&n.catch(function(){})}}catch(e){if(e&&r&&typeof e.stack==`string`)return[e.stack,r.stack]}return[null,null]}};r.DetermineComponentFrameRoot.displayName=`DetermineComponentFrameRoot`;var i=Object.getOwnPropertyDescriptor(r.DetermineComponentFrameRoot,`name`);i&&i.configurable&&Object.defineProperty(r.DetermineComponentFrameRoot,"name",{value:`DetermineComponentFrameRoot`});var a=r.DetermineComponentFrameRoot(),o=a[0],s=a[1];if(o&&s){var c=o.split(`
`),l=s.split(`
`);for(i=r=0;r<c.length&&!c[r].includes(`DetermineComponentFrameRoot`);)r++;for(;i<l.length&&!l[i].includes(`DetermineComponentFrameRoot`);)i++;if(r===c.length||i===l.length)for(r=c.length-1,i=l.length-1;1<=r&&0<=i&&c[r]!==l[i];)i--;for(;1<=r&&0<=i;r--,i--)if(c[r]!==l[i]){if(r!==1||i!==1)do if(r--,i--,0>i||c[r]!==l[i]){var u=`
`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(`<anonymous>`)&&(u=u.replace(`<anonymous>`,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{xe=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?be(n):``}function Ce(e,t){switch(e.tag){case 26:case 27:case 5:return be(e.type);case 16:return be(`Lazy`);case 13:return e.child!==t&&t!==null?be(`Suspense Fallback`):be(`Suspense`);case 19:return be(`SuspenseList`);case 0:case 15:return Se(e.type,!1);case 11:return Se(e.type.render,!1);case 1:return Se(e.type,!0);case 31:return be(`Activity`);default:return``}}function we(e){try{var t=``,n=null;do t+=Ce(e,n),n=e,e=e.return;while(e);return t}catch(e){return`
Error generating stack: `+e.message+`
`+e.stack}}var Te=Object.prototype.hasOwnProperty,Ee=t.unstable_scheduleCallback,De=t.unstable_cancelCallback,Oe=t.unstable_shouldYield,ke=t.unstable_requestPaint,Ae=t.unstable_now,P=t.unstable_getCurrentPriorityLevel,je=t.unstable_ImmediatePriority,Me=t.unstable_UserBlockingPriority,Ne=t.unstable_NormalPriority,Pe=t.unstable_LowPriority,Fe=t.unstable_IdlePriority,Ie=t.log,Le=t.unstable_setDisableYieldValue,Re=null,ze=null;function Be(e){if(typeof Ie==`function`&&Le(e),ze&&typeof ze.setStrictMode==`function`)try{ze.setStrictMode(Re,e)}catch{}}var Ve=Math.clz32?Math.clz32:We,He=Math.log,Ue=Math.LN2;function We(e){return e>>>=0,e===0?32:31-(He(e)/Ue|0)|0}var Ge=256,Ke=262144,qe=4194304;function Je(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Ye(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Je(n))):i=Je(o):i=Je(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Je(n))):i=Je(o)):i=Je(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function Xe(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Ze(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Qe(){var e=qe;return qe<<=1,!(qe&62914560)&&(qe=4194304),e}function $e(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function et(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function tt(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0<n;){var u=31-Ve(n),d=1<<u;s[u]=0,c[u]=-1;var f=l[u];if(f!==null)for(l[u]=null,u=0;u<f.length;u++){var p=f[u];p!==null&&(p.lane&=-536870913)}n&=~d}r!==0&&nt(e,r,0),a!==0&&i===0&&e.tag!==0&&(e.suspendedLanes|=a&~(o&~t))}function nt(e,t,n){e.pendingLanes|=t,e.suspendedLanes&=~t;var r=31-Ve(t);e.entangledLanes|=t,e.entanglements[r]=e.entanglements[r]|1073741824|n&261930}function rt(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-Ve(n),i=1<<r;i&t|e[r]&t&&(e[r]|=t),n&=~i}}function it(e,t){var n=t&-t;return n=n&42?1:at(n),(n&(e.suspendedLanes|t))===0?n:0}function at(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:e=128;break;case 268435456:e=134217728;break;default:e=0}return e}function ot(e){return e&=-e,2<e?8<e?e&134217727?32:268435456:8:2}function st(){var e=k.p;return e===0?(e=window.event,e===void 0?32:mp(e.type)):e}function ct(e,t){var n=k.p;try{return k.p=e,t()}finally{k.p=n}}var lt=Math.random().toString(36).slice(2),ut=`__reactFiber$`+lt,dt=`__reactProps$`+lt,ft=`__reactContainer$`+lt,F=`__reactEvents$`+lt,pt=`__reactListeners$`+lt,mt=`__reactHandles$`+lt,ht=`__reactResources$`+lt,gt=`__reactMarker$`+lt;function _t(e){delete e[ut],delete e[dt],delete e[F],delete e[pt],delete e[mt]}function vt(e){var t=e[ut];if(t)return t;for(var n=e.parentNode;n;){if(t=n[ft]||n[ut]){if(n=t.alternate,t.child!==null||n!==null&&n.child!==null)for(e=df(e);e!==null;){if(n=e[ut])return n;e=df(e)}return t}e=n,n=e.parentNode}return null}function yt(e){if(e=e[ut]||e[ft]){var t=e.tag;if(t===5||t===6||t===13||t===31||t===26||t===27||t===3)return e}return null}function bt(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e.stateNode;throw Error(i(33))}function xt(e){var t=e[ht];return t||=e[ht]={hoistableStyles:new Map,hoistableScripts:new Map},t}function St(e){e[gt]=!0}var Ct=new Set,wt={};function Tt(e,t){Et(e,t),Et(e+`Capture`,t)}function Et(e,t){for(wt[e]=t,e=0;e<t.length;e++)Ct.add(t[e])}var Dt=RegExp(`^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\Line truncated
`).replace(kd,``)}function jd(e,t){return t=Ad(t),Ad(e)===t}function Md(e,t,n,r,a,o){switch(n){case`children`:typeof r==`string`?t===`body`||t===`textarea`&&r===``||Jt(e,r):(typeof r==`number`||typeof r==`bigint`)&&t!==`body`&&Jt(e,``+r);break;case`className`:Mt(e,`class`,r);break;case`tabIndex`:Mt(e,`tabindex`,r);break;case`dir`:case`role`:case`viewBox`:case`width`:case`height`:Mt(e,n,r);break;case`style`:Zt(e,r,o);break;case`data`:if(t!==`object`){Mt(e,`data`,r);break}case`src`:case`href`:if(r===``&&(t!==`a`||n!==`href`)){e.removeAttribute(n);break}if(r==null||typeof r==`function`||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=tn(``+r),e.setAttribute(n,r);break;case`action`:case`formAction`:if(typeof r==`function`){e.setAttribute(n,`javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')`);break}else typeof o==`function`&&(n===`formAction`?(t!==`input`&&Md(e,t,`name`,a.name,a,null),Md(e,t,`formEncType`,a.formEncType,a,null),Md(e,t,`formMethod`,a.formMethod,a,null),Md(e,t,`formTarget`,a.formTarget,a,null)):(Md(e,t,`encType`,a.encType,a,null),Md(e,t,`method`,a.method,a,null),Md(e,t,`target`,a.target,a,null)));if(r==null||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=tn(``+r),e.setAttribute(n,r);break;case`onClick`:r!=null&&(e.onclick=nn);break;case`onScroll`:r!=null&&$(`scroll`,e);break;case`onScrollEnd`:r!=null&&$(`scrollend`,e);break;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));e.innerHTML=n}}break;case`multiple`:e.multiple=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`muted`:e.muted=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`suppressContentEditableWarning`:case`suppressHydrationWarning`:case`defaultValue`:case`defaultChecked`:case`innerHTML`:case`ref`:break;case`autoFocus`:break;case`xlinkHref`:if(r==null||typeof r==`function`||typeof r==`boolean`||typeof r==`symbol`){e.removeAttribute(`xlink:href`);break}n=tn(``+r),e.setAttributeNS(`http://www.w3.org/1999/xlink`,`xlink:href`,n);break;case`contentEditable`:case`spellCheck`:case`draggable`:case`value`:case`autoReverse`:case`externalResourcesRequired`:case`focusable`:case`preserveAlpha`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,``+r):e.removeAttribute(n);break;case`inert`:case`allowFullScreen`:case`async`:case`autoPlay`:case`controls`:case`default`:case`defer`:case`disabled`:case`disablePictureInPicture`:case`disableRemotePlayback`:case`formNoValidate`:case`hidden`:case`loop`:case`noModule`:case`noValidate`:case`open`:case`playsInline`:case`readOnly`:case`required`:case`reversed`:case`scoped`:case`seamless`:case`itemScope`:r&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,``):e.removeAttribute(n);break;case`capture`:case`download`:!0===r?e.setAttribute(n,``):!1!==r&&r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,r):e.removeAttribute(n);break;case`cols`:case`rows`:case`size`:case`span`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`&&!isNaN(r)&&1<=r?e.setAttribute(n,r):e.removeAttribute(n);break;case`rowSpan`:case`start`:r==null||typeof r==`function`||typeof r==`symbol`||isNaN(r)?e.removeAttribute(n):e.setAttribute(n,r);break;case`popover`:$(`beforetoggle`,e),$(`toggle`,e),jt(e,`popover`,r);break;case`xlinkActuate`:Nt(e,`http://www.w3.org/1999/xlink`,`xlink:actuate`,r);break;case`xlinkArcrole`:Nt(e,`http://www.w3.org/1999/xlink`,`xlink:arcrole`,r);break;case`xlinkRole`:Nt(e,`http://www.w3.org/1999/xlink`,`xlink:role`,r);break;case`xlinkShow`:Nt(e,`http://www.w3.org/1999/xlink`,`xlink:show`,r);break;case`xlinkTitle`:Nt(e,`http://www.w3.org/1999/xlink`,`xlink:title`,r);break;case`xlinkType`:Nt(e,`http://www.w3.org/1999/xlink`,`xlink:type`,r);break;case`xmlBase`:Nt(e,`http://www.w3.org/XML/1998/namespace`,`xml:base`,r);break;case`xmlLang`:Nt(e,`http://www.w3.org/XML/1998/namespace`,`xml:lang`,r);break;case`xmlSpace`:Nt(e,`http://www.w3.org/XML/1998/namespace`,`xml:space`,r);break;case`is`:jt(e,`is`,r);break;case`innerText`:case`textContent`:break;default:(!(2<n.length)||n[0]!==`o`&&n[0]!==`O`||n[1]!==`n`&&n[1]!==`N`)&&(n=$t.get(n)||n,jt(e,n,r))}}function Nd(e,t,n,r,a,o){switch(n){case`style`:Zt(e,r,o);break;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));e.innerHTML=n}}break;case`children`:typeof r==`string`?Jt(e,r):(typeof r==`number`||typeof r==`bigint`)&&Jt(e,``+r);break;case`onScroll`:r!=null&&$(`scroll`,e);break;case`onScrollEnd`:r!=null&&$(`scrollend`,e);break;case`onClick`:r!=null&&(e.onclick=nn);break;case`suppressContentEditableWarning`:case`suppressHydrationWarning`:case`innerHTML` Line truncated
@layer {
[${Fc}] {
touch-action: pan-x pan-y pinch-zoom;
}
}
`.trim(),e.head.prepend(t)},[f]),(0,D.useEffect)(()=>{let e=g.current;return()=>{d||kc(e.target??void 0);for(let t of e.disposables)t();e.disposables=[]}},[d]),{isPressed:c||m,pressProps:Fe(p,ie,{[Fc]:!0})}}function Lc(e){return e.tagName===`A`&&e.hasAttribute(`href`)}function Rc(e,t){let{key:n,code:r}=e,i=t,a=i.getAttribute(`role`);return(n===`Enter`||n===` `||n===`Spacebar`||r===`Space`)&&!(i instanceof Se(i).HTMLInputElement&&!Uc(i,n)||i instanceof Se(i).HTMLTextAreaElement||i.isContentEditable)&&!((a===`link`||!a&&Lc(i))&&n!==`Enter`)}function zc(e,t){let n=t.clientX,r=t.clientY;return{currentTarget:e,shiftKey:t.shiftKey,ctrlKey:t.ctrlKey,metaKey:t.metaKey,altKey:t.altKey,clientX:n,clientY:r,key:t.key}}function Bc(e){return e instanceof HTMLInputElement?!1:e instanceof HTMLButtonElement?e.type!==`submit`&&e.type!==`reset`:!Lc(e)}function Vc(e,t){return b()&&t===`Enter`?!1:e instanceof HTMLInputElement?t===`Enter`&&(e.type===`checkbox`||e.type===`radio`)?!1:!Uc(e,t):Bc(e)}var Hc=new Set([`checkbox`,`radio`,`range`,`color`,`file`,`image`,`button`,`submit`,`reset`]);function Uc(e,t){return e.type===`checkbox`||e.type===`radio`?t===` `:Hc.has(e.type)}function Wc(e,t){let{elementType:n=`button`,isDisabled:r,onPress:i,onPressStart:a,onPressEnd:o,onPressUp:s,onPressChange:c,preventFocusOnPress:l,allowFocusWhenDisabled:u,onClick:d,href:f,target:p,rel:m,type:h=`button`}=e,g;g=n===`button`?{type:h,disabled:r,form:e.form,formAction:e.formAction,formEncType:e.formEncType,formMethod:e.formMethod,formNoValidate:e.formNoValidate,formTarget:e.formTarget,name:e.name,value:e.value}:{role:`button`,href:n===`a`&&!r?f:void 0,target:n===`a`?p:void 0,type:n===`input`?h:void 0,disabled:n===`input`?r:void 0,"aria-disabled":!r||n===`input`?void 0:r,rel:n===`a`?m:void 0};let{pressProps:_,isPressed:v}=Ic({onPressStart:a,onPressEnd:o,onPressChange:c,onPress:i,onPressUp:s,onClick:d,isDisabled:r,preventFocusOnPress:l,ref:t}),{focusableProps:y}=wc(e,t);u&&(y.tabIndex=r?-1:y.tabIndex);let b=Fe(y,_,He(e,{labelable:!0}));return{isPressed:v,buttonProps:Fe(g,b,{"aria-haspopup":e[`aria-haspopup`],"aria-expanded":e[`aria-expanded`],"aria-controls":e[`aria-controls`],"aria-pressed":e[`aria-pressed`],"aria-current":e[`aria-current`],"aria-disabled":e[`aria-disabled`]})}}function Gc(e){let{isDisabled:t,onBlurWithin:n,onFocusWithin:r,onFocusWithinChange:i}=e,a=(0,D.useRef)({isFocusWithin:!1}),{addGlobalListener:o,removeAllGlobalListeners:s}=Je(),c=(0,D.useCallback)(e=>{ke(e.currentTarget,P(e))&&a.current.isFocusWithin&&!ke(e.currentTarget,e.relatedTarget)&&(a.current.isFocusWithin=!1,s(),n&&n(e),i&&i(!1))},[n,i,a,s]),l=Is(c),u=(0,D.useCallback)(e=>{if(!ke(e.currentTarget,P(e)))return;let t=P(e),n=xe(t),s=Ae(n);if(!a.current.isFocusWithin&&s===t){r&&r(e),i&&i(!0),a.current.isFocusWithin=!0,l(e);let t=e.currentTarget;o(n,`focus`,e=>{let r=P(e);if(a.current.isFocusWithin&&!ke(t,r)){let e=new n.defaultView.FocusEvent(`blur`,{relatedTarget:r});Fs(e,t),c(Ps(e))}},{capture:!0})}},[r,i,l,o,c]);return t?{focusWithinProps:{onFocus:void 0,onBlur:void 0}}:{focusWithinProps:{onFocus:u,onBlur:c}}}function Kc(e={}){let{autoFocus:t=!1,isTextInput:n,within:r}=e,i=(0,D.useRef)({isFocused:!1,isFocusVisible:t||tc()}),[a,o]=(0,D.useState)(!1),[s,c]=(0,D.useState)(()=>i.current.isFocused&&i.current.isFocusVisible),l=(0,D.useCallback)(()=>c(i.current.isFocused&&i.current.isFocusVisible),[]),u=(0,D.useCallback)(e=>{i.current.isFocused=e,i.current.isFocusVisible=tc(),o(e),l()},[l]);ac(e=>{i.current.isFocusVisible=e,l()},[n,a],{enabled:a,isTextInput:n});let{focusProps:d}=sc({isDisabled:r,onFocusChange:u}),{focusWithinProps:f}=Gc({isDisabled:!r,onFocusWithinChange:u});return{isFocused:a,isFocusVisible:s,focusProps:r?f:d}}var qc=!1,Jc=0;function Yc(){qc=!0,setTimeout(()=>{qc=!1},500)}function Xc(e){e.pointerType===`touch`&&Yc()}function Zc(){let e=xe(null);if(e!==void 0)return Jc===0&&typeof PointerEvent<`u`&&e.addEventListener(`pointerup`,Xc),Jc++,()=>{Jc--,!(Jc>0)&&typeof PointerEvent<`u`&&e.removeEventListener(`pointerup`,Xc)}}function Qc(e){let{onHoverStart:t,onHoverChange:n,onHoverEnd:r,isDisabled:i}=e,[a,o]=(0,D.useState)(!1),s=(0,D.useRef)({isHovered:!1,ignoreEmulatedMouseEvents:!1,pointerType:``,target:null}).current;(0,D.useEffect)(Zc,[]);let{addGlobalListener:c,removeAllGlobalListeners:l}=Je(),{hoverProps:u,triggerHoverEnd:d}=(0,D.useMemo)(()=>{let e=(e,r)=>{if(s.pointerType=r,i||r===`touch`||s.isHovered||!ke(e.currentTarget,P(e)))return;s.isHovered=!0;let l=e.currentTarget;s.target=l,c(xe(P(e)),`pointerover`,e=>{s.isHovered&&s.target&&!ke(s.target,P(e))&&a(e,e.pointerType)},{capture:!0}),t&&t({type:`hoverstart`,target:l,pointerType:r}),n&&n(!0),o(!0)},a=(e,t)=>{let i=s.target;s.pointerType=``,s.target=null,!(t===`touch`||!s.isHovered||!i)&&(s.isHovered=!1,l(),r&&r({type:`hoverend`,target:i,pointerType:t}),n&&n(!1),o(!1))},u={};return typeof PointerEvent<`u`&&(u.onPointerEnter=t=>{qc&&t.pointerType===`mouse`||e(t,t.pointerType)},u.onPointerLeave=e=>{!i&&ke(e Line truncated
+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
<rect width="64" height="64" rx="14" fill="#09090b" />
<circle cx="32" cy="32" r="15" fill="none" stroke="#fafafa" stroke-width="3.5" />
<circle cx="32" cy="32" r="5.5" fill="#fafafa" />
<path d="M32 9v5M32 50v5M9 32h5M50 32h5" stroke="#71717a" stroke-width="3.5" stroke-linecap="round" />
</svg>

After

Width:  |  Height:  |  Size: 389 B

+22
View File
@@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!-- viewport-fit=cover so the sticky header sits under the notch rather than beside it. -->
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="color-scheme" content="light dark" />
<meta name="theme-color" content="#fafafa" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#09090b" media="(prefers-color-scheme: dark)" />
<title>grok-glance</title>
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<link rel="apple-touch-icon" href="/icon.svg" />
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="glance" />
<script type="module" crossorigin src="/assets/index-D-dJ5pn0.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BZSiLyex.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
+19
View File
@@ -0,0 +1,19 @@
{
"name": "grok-glance",
"short_name": "glance",
"description": "Glance at what Grok Build is doing.",
"start_url": "/",
"scope": "/",
"display": "standalone",
"orientation": "portrait",
"background_color": "#09090b",
"theme_color": "#09090b",
"icons": [
{
"src": "/icon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any"
}
]
}
+1
View File
@@ -21,6 +21,7 @@
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "6.0.2",
"rolldown": "1.0.3",
"tailwind-variants": "3.3.0",
"tailwindcss": "4.3.1",
"typescript": "5.6.3",
+4 -2
View File
@@ -9,11 +9,12 @@
},
"scripts": {
"build": "npm run build:server && npm run build:web",
"build:server": "tsc -p tsconfig.server.json",
"build:server": "tsc -p tsconfig.server.json && rolldown server/src/index.ts -o dist/server/index.js -f esm -p node",
"build:web": "tsc -p tsconfig.web.json && vite build",
"dev": "vite",
"start": "node dist/server/index.js",
"glance": "node bin/glance"
"glance": "node bin/glance",
"check:dist": "npm run build && test -z \"$(git status --porcelain dist)\""
},
"dependencies": {
"@heroui/react": "3.2.4",
@@ -29,6 +30,7 @@
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "6.0.2",
"rolldown": "1.0.3",
"tailwind-variants": "3.3.0",
"tailwindcss": "4.3.1",
"typescript": "5.6.3",
+3 -3
View File
@@ -18,7 +18,6 @@ import http from "node:http";
import crypto from "node:crypto";
import { URL } from "node:url";
import {
DEFAULT_PORT,
VERSION,
deriveRpId,
ensureHome,
@@ -564,8 +563,9 @@ server.listen(cfg.port, cfg.host, () => {
console.log(`[glance] state: ${paths.home}`);
console.log(`[glance] origin: ${cfg.origin ?? "(none set - see README)"} rpId: ${cfg.rpId ?? "localhost"}`);
console.log(`[glance] accepts assertions from: ${expectedOrigins(cfg).join(", ")}`);
if (!webBuildExists()) console.log("[glance] web app not built yet: npm install && npm run build");
if (cfg.port !== DEFAULT_PORT) console.log(`[glance] note: non-default port, run \`glance sync-hooks\``);
// dist/ is committed, so this only fires for a developer who deleted it. Nothing warns about a
// non-default port any more: the hook scripts read config.json themselves.
if (!webBuildExists()) console.log("[glance] web app missing from dist/: npm install && npm run build");
});
server.on("error", (err) => {
+1 -1
View File
@@ -32,7 +32,7 @@ export function webBuildExists(): boolean {
export function serveStatic(urlPath: string, res: ServerResponse): void {
if (!webBuildExists()) {
res.writeHead(503, { ...SECURITY_HEADERS, "content-type": "text/plain; charset=utf-8" });
res.end("grok-glance: web app not built yet. Run `npm install && npm run build`.\n");
res.end("grok-glance: dist/web is missing. Run `npm install && npm run build`.\n");
return;
}
+7 -10
View File
@@ -14,16 +14,12 @@ same dashboard — no per-session setup.
The daemon is started automatically by the `SessionStart` hook. Everything below is done through
the `glance` CLI at `$GROK_PLUGIN_ROOT/bin/glance`.
## First check whether it is even built
## No build step
The plugin ships as TypeScript and must be built once:
```sh
cd "$GROK_PLUGIN_ROOT" && npm install && npm run build
```
`glance status` prints a "not built" error with this same instruction if it is missing. Do not
attempt to skip the build — the daemon entry point is `dist/server/index.js`.
The plugin ships prebuilt: `dist/` is committed, and the daemon is a single dependency-free
bundle. A clone is ready to run. Only reach for `npm install && npm run build` in
`$GROK_PLUGIN_ROOT` if `glance status` actually says it is not built, which means `dist/` was
deleted from the checkout.
## The commands
@@ -99,7 +95,8 @@ and wait for a tap on the phone. Defaults that matter:
secret in `$GLANCE_HOME/hook.secret` no longer matches the one the daemon loaded (events are being
dropped with a 403); `glance stop && glance up` fixes that. Otherwise check that
`hooks/hooks.json` exists and that the plugin is registered with Grok Build.
- **Page says "run npm install && npm run build"** → the web bundle is missing; build it.
- **Page says "run npm install && npm run build"** → `dist/web` is missing from the checkout,
which should not happen in a clone. Re-clone, or build it.
- **Only one agent shows up** → the others were started before the plugin was installed, or in an
environment where the hooks are not registered. A session appears on its next hook event; nothing
can be back-filled for one that already ran.
+2 -4
View File
@@ -5,8 +5,7 @@
"module": "NodeNext",
"moduleResolution": "NodeNext",
"types": ["node"],
"outDir": "dist/server",
"rootDir": "server/src",
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
@@ -15,8 +14,7 @@
"skipLibCheck": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"declaration": false,
"sourceMap": true
"declaration": false
},
"include": ["server/src"]
}