@grum-os/cli 0.3.37 → 0.3.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/content.md5 +1 -1
- package/grum.mjs +6 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -91,9 +91,9 @@ These are the files setup writes. They merge into what is already there, and a s
|
|
|
91
91
|
Codex keeps project configuration and hooks under your control. Grum will not appear in MCP or Hooks until you do both of these:
|
|
92
92
|
|
|
93
93
|
1. Trust this folder when Codex asks. Then in **Settings > MCP servers**, turn on **grum**. If Codex labels the server with this folder's name, pick the one whose command runs `grum.mjs`.
|
|
94
|
-
2. Open **Settings > Hooks
|
|
94
|
+
2. Open **Settings > Hooks** for this project and check Grum's 3 commands (they say "need review" and run `grum.mjs hook ...`). Turn on any that are off; this is separate from MCP. In the terminal app, run `/hooks` to review the list.
|
|
95
95
|
|
|
96
|
-
Without step 2 the tools still work and the agent still follows the guidance, but nothing checks when Codex stops and `git commit` and `git push` are not refused. Until a hook
|
|
96
|
+
Without step 2 the tools still work and the agent still follows the guidance, but nothing checks when Codex stops and `git commit` and `git push` are not refused. Until Grum observes a successful hook run, it reports that hook status is unconfirmed.
|
|
97
97
|
|
|
98
98
|
| File | What it does |
|
|
99
99
|
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
package/content.md5
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
3106fdfb601b20e9df95c8e127d60964
|
package/grum.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
globalThis.__GRUM_VERSION__="0.3.
|
|
2
|
+
globalThis.__GRUM_VERSION__="0.3.38";
|
|
3
3
|
import{chmodSync as t1,existsSync as mt,readdirSync as nb,rmSync as xo}from"node:fs";import{homedir as cb}from"node:os";import{fileURLToPath as n1}from"node:url";import te from"node:path";import{createInterface as r1,emitKeypressEvents as o1}from"node:readline";var Ro=(e,t,n)=>Math.min(n,Math.max(t,e)),So=e=>Ro(Math.round(e),0,255).toString(16).padStart(2,"0").toUpperCase();function Ao({r:e,g:t,b:n,a:r}){let o=`#${So(e)}${So(t)}${So(n)}`,s=Math.round(Ro(r,0,1)*255);return s>=255?o:`${o}${So(s)}`}function wb(e){let t=e.replace(/^#/,"");if(!/^[0-9a-f]+$/i.test(t)||![3,4,6,8].includes(t.length))return null;let n=t.length<=4?[...t].map(s=>s+s).join(""):t,r=parseInt(n.slice(0,6),16),o=n.length===8?parseInt(n.slice(6,8),16)/255:1;return{r:r>>16&255,g:r>>8&255,b:r&255,a:o}}function Co(e,t){let n=e.trim();return n.endsWith("%")?parseFloat(n)/100*t:parseFloat(n)}function kb(e,t,n){let r=s=>(s+e/30)%12,o=s=>n-t*Math.min(n,1-n)*Math.max(-1,Math.min(r(s)-3,9-r(s),1));return[o(0)*255,o(8)*255,o(4)*255]}function $o(e,t){return e==="none"?0:Co(e,t)}function uu(e,t,n){let r=(e+.3963377774*t+.2158037573*n)**3,o=(e-.1055613458*t-.0638541728*n)**3,s=(e-.0894841775*t-1.291485548*n)**3,i=[4.0767416621*r-3.3077115913*o+.2309699292*s,-1.2684380046*r+2.6097574011*o-.3413193965*s,-.0041960863*r-.7034186147*o+1.707614701*s],[l,a,c]=i.map(u=>{let p=Ro(u,0,1);return(p<=.0031308?12.92*p:1.055*p**(1/2.4)-.055)*255});return[l,a,c]}function ht(e){let t=e.trim().toLowerCase();if(t.startsWith("#"))return wb(t);let n=t.match(/^(rgba?|hsla?|oklch|oklab)\(([^)]*)\)$/);if(!n?.[1]||n[2]==null)return null;let r=n[2].split(/[\s,/]+/).filter(Boolean);if(r.length<3)return null;let o=r[3]!=null?$o(r[3],1):1;if(n[1].startsWith("ok")){let p=Ro($o(r[0],1),0,1),d=$o(r[1],.4);if(n[1]==="oklab"){let b=$o(r[2],.4);if([p,d,b,o].some(Number.isNaN))return null;let[k,y,x]=uu(p,d,b);return{r:k,g:y,b:x,a:o}}let f=r[2];if(f!=="none"&&!/^-?[\d.]+(deg)?$/.test(f))return null;let m=(f==="none"?0:parseFloat(f))*Math.PI/180;if([p,d,m,o].some(Number.isNaN))return null;let[g,S,h]=uu(p,d*Math.cos(m),d*Math.sin(m));return{r:g,g:S,b:h,a:o}}if(n[1].startsWith("rgb")){let[p,d,f]=r.slice(0,3).map(m=>Co(m,255));return[p,d,f,o].some(Number.isNaN)?null:{r:p,g:d,b:f,a:o}}let s=parseFloat(r[0]),i=Co(r[1],1),l=Co(r[2],1);if([s,i,l,o].some(Number.isNaN))return null;let[a,c,u]=kb((s%360+360)%360,i,l);return{r:a,g:c,b:u,a:o}}function pe(e){let t=ht(e);return t?Ao(t):null}function pu({r:e,g:t,b:n}){let r=p=>{let d=p/255;return d<=.04045?d/12.92:((d+.055)/1.055)**2.4},[o,s,i]=[r(e),r(t),r(n)],l=(o*.4124+s*.3576+i*.1805)/.95047,a=o*.2126+s*.7152+i*.0722,c=(o*.0193+s*.1192+i*.9505)/1.08883,u=p=>p>.008856?Math.cbrt(p):7.787*p+16/116;return[116*u(a)-16,500*(u(l)-u(a)),200*(u(a)-u(c))]}function Ri(e){return`--color-${e.replace(/^#/,"").slice(0,6).toLowerCase()}`}function Io(e,t){let n=ht(e),r=ht(t);if(!n||!r)return 1/0;let[o,s,i]=pu(n),[l,a,c]=pu(r);return Math.hypot(o-l,s-a,i-c)}var hr=/#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3,4})\b|\b(?:rgba?|hsla?|oklch|oklab)\([^)]*\)/gi;var Ai={slate:{50:"#f8fafc",100:"#f1f5f9",200:"#e2e8f0",300:"#cbd5e1",400:"#94a3b8",500:"#64748b",600:"#475569",700:"#334155",800:"#1e293b",900:"#0f172a",950:"#020617"},gray:{50:"#f9fafb",100:"#f3f4f6",200:"#e5e7eb",300:"#d1d5db",400:"#9ca3af",500:"#6b7280",600:"#4b5563",700:"#374151",800:"#1f2937",900:"#111827",950:"#030712"},zinc:{50:"#fafafa",100:"#f4f4f5",200:"#e4e4e7",300:"#d4d4d8",400:"#a1a1aa",500:"#71717a",600:"#52525b",700:"#3f3f46",800:"#27272a",900:"#18181b",950:"#09090b"},neutral:{50:"#fafafa",100:"#f5f5f5",200:"#e5e5e5",300:"#d4d4d4",400:"#a3a3a3",500:"#737373",600:"#525252",700:"#404040",800:"#262626",900:"#171717",950:"#0a0a0a"},stone:{50:"#fafaf9",100:"#f5f5f4",200:"#e7e5e4",300:"#d6d3d1",400:"#a8a29e",500:"#78716c",600:"#57534e",700:"#44403c",800:"#292524",900:"#1c1917",950:"#0c0a09"},red:{50:"#fef2f2",100:"#fee2e2",200:"#fecaca",300:"#fca5a5",400:"#f87171",500:"#ef4444",600:"#dc2626",700:"#b91c1c",800:"#991b1b",900:"#7f1d1d",950:"#450a0a"},orange:{50:"#fff7ed",100:"#ffedd5",200:"#fed7aa",300:"#fdba74",400:"#fb923c",500:"#f97316",600:"#ea580c",700:"#c2410c",800:"#9a3412",900:"#7c2d12",950:"#431407"},amber:{50:"#fffbeb",100:"#fef3c7",200:"#fde68a",300:"#fcd34d",400:"#fbbf24",500:"#f59e0b",600:"#d97706",700:"#b45309",800:"#92400e",900:"#78350f",950:"#451a03"},yellow:{50:"#fefce8",100:"#fef9c3",200:"#fef08a",300:"#fde047",400:"#facc15",500:"#eab308",600:"#ca8a04",700:"#a16207",800:"#854d0e",900:"#713f12",950:"#422006"},lime:{50:"#f7fee7",100:"#ecfccb",200:"#d9f99d",300:"#bef264",400:"#a3e635",500:"#84cc16",600:"#65a30d",700:"#4d7c0f",800:"#3f6212",900:"#365314",950:"#1a2e05"},green:{50:"#f0fdf4",100:"#dcfce7",200:"#bbf7d0",300:"#86efac",400:"#4ade80",500:"#22c55e",600:"#16a34a",700:"#15803d",800:"#166534",900:"#14532d",950:"#052e16"},emerald:{50:"#ecfdf5",100:"#d1fae5",200:"#a7f3d0",300:"#6ee7b7",400:"#34d399",500:"#10b981",600:"#059669",700:"#047857",800:"#065f46",900:"#064e3b",950:"#022c22"},teal:{50:"#f0fdfa",100:"#ccfbf1",200:"#99f6e4",300:"#5eead4",400:"#2dd4bf",500:"#14b8a6",600:"#0d9488",700:"#0f766e",800:"#115e59",900:"#134e4a",950:"#042f2e"},cyan:{50:"#ecfeff",100:"#cffafe",200:"#a5f3fc",300:"#67e8f9",400:"#22d3ee",500:"#06b6d4",600:"#0891b2",700:"#0e7490",800:"#155e75",900:"#164e63",950:"#083344"},sky:{50:"#f0f9ff",100:"#e0f2fe",200:"#bae6fd",300:"#7dd3fc",400:"#38bdf8",500:"#0ea5e9",600:"#0284c7",700:"#0369a1",800:"#075985",900:"#0c4a6e",950:"#082f49"},blue:{50:"#eff6ff",100:"#dbeafe",200:"#bfdbfe",300:"#93c5fd",400:"#60a5fa",500:"#3b82f6",600:"#2563eb",700:"#1d4ed8",800:"#1e40af",900:"#1e3a8a",950:"#172554"},indigo:{50:"#eef2ff",100:"#e0e7ff",200:"#c7d2fe",300:"#a5b4fc",400:"#818cf8",500:"#6366f1",600:"#4f46e5",700:"#4338ca",800:"#3730a3",900:"#312e81",950:"#1e1b4b"},violet:{50:"#f5f3ff",100:"#ede9fe",200:"#ddd6fe",300:"#c4b5fd",400:"#a78bfa",500:"#8b5cf6",600:"#7c3aed",700:"#6d28d9",800:"#5b21b6",900:"#4c1d95",950:"#2e1065"},purple:{50:"#faf5ff",100:"#f3e8ff",200:"#e9d5ff",300:"#d8b4fe",400:"#c084fc",500:"#a855f7",600:"#9333ea",700:"#7e22ce",800:"#6b21a8",900:"#581c87",950:"#3b0764"},fuchsia:{50:"#fdf4ff",100:"#fae8ff",200:"#f5d0fe",300:"#f0abfc",400:"#e879f9",500:"#d946ef",600:"#c026d3",700:"#a21caf",800:"#86198f",900:"#701a75",950:"#4a044e"},pink:{50:"#fdf2f8",100:"#fce7f3",200:"#fbcfe8",300:"#f9a8d4",400:"#f472b6",500:"#ec4899",600:"#db2777",700:"#be185d",800:"#9d174d",900:"#831843",950:"#500724"},rose:{50:"#fff1f2",100:"#ffe4e6",200:"#fecdd3",300:"#fda4af",400:"#fb7185",500:"#f43f5e",600:"#e11d48",700:"#be123c",800:"#9f1239",900:"#881337",950:"#4c0519"}};function du(e){let t=e.lastIndexOf("-");return t<=0?null:Ai[e.slice(0,t)]?.[e.slice(t+1)]??null}function jn(e){let t=[],n=0,r=0;for(let o=0;o<e.length;o++){let s=e[o];s==="("||s==="["?n++:s===")"||s==="]"?n=Math.max(0,n-1):s===","&&n===0&&(t.push(e.slice(r,o).trim()),r=o+1)}return t.push(e.slice(r).trim()),t.filter(Boolean)}function xb(e,t){if(!e)return t;let n=jn(e);return jn(t).flatMap(r=>n.map(o=>r.includes("&")?r.replaceAll("&",o):`${o} ${r}`)).join(", ")}var vb=/^@(?:-\w+-)?(?:keyframes|font-face|property)\b/i;function Eo(e){let t=e.replace(/\/\*[\s\S]*?\*\//g,i=>i.replace(/[^\n]/g," ")),n=[],r=[],o=0,s=1;for(let i=0;i<t.length;i++){let l=t[i];if(l===`
|
|
4
4
|
`&&s++,l==="{"){let a=t.slice(o,i).trim(),c=r[r.length-1];c&&(c.own+=t.slice(c.segStart,o));let u=/^@theme\b/i.test(a),p=a.startsWith("@")&&!u,d=[...r].reverse().find(f=>!f.isAt)??null;r.push({raw:a,resolved:p?a:u?":root":xb(d?.resolved??null,a),isAt:p,skip:(c?.skip??!1)||p&&vb.test(a),line:s,own:"",segStart:i+1}),o=i+1}else if(l==="}"){let a=r.pop();if(a){a.own+=t.slice(a.segStart,i),!a.isAt&&!a.skip&&n.push({selector:a.resolved,body:a.own,line:a.line,context:r.filter(u=>u.isAt).map(u=>u.raw.replace(/\s+/g," ")).join(" \xB7 ")});let c=r[r.length-1];c&&(c.segStart=i+1)}o=i+1}else l===";"&&(o=i+1)}return n}function To(e){let t=[],n=0,r=0,o=s=>{let i=s.indexOf(":");if(i<=0)return;let l=s.slice(0,i).trim(),a=s.slice(i+1).replace(/\s+/g," ").trim();/^-{0,2}[a-z][\w-]*$/i.test(l)&&a&&t.push({property:l,value:a})};for(let s=0;s<e.length;s++){let i=e[s];i==="("?n++:i===")"?n=Math.max(0,n-1):i===";"&&n===0&&(o(e.slice(r,s)),r=s+1)}return o(e.slice(r)),t}var fu=e=>e.replace(/\\(.)/g,"$1");function Sb(e){let t=e;for(;;){let n=/:(?:global|local)\(/i.exec(t);if(!n)break;let r=0,o=-1;for(let i=n.index+n[0].length-1;i<t.length;i++){let l=t[i];if(l==="(")r++;else if(l===")"&&--r===0){o=i;break}}if(o<0)break;let s=t.slice(n.index+n[0].length,o).trim();t=`${t.slice(0,n.index)}${s}${t.slice(o+1)}`}return t.replace(/:(?:global|local)(?=\s|$)/gi,"").trim()}var Ii=/\*|[a-z][\w-]*|\.((?:\\.|[\w-])+)|#((?:\\.|[\w-])+)|\[\s*([\w-]+)\s*(?:([~|^$*]?=)\s*(?:"([^"]*)"|'([^']*)'|([^\]\s]*)))?\s*[is]?\s*\]|::([\w-]+)|:([\w-]+)(\((?:[^()]|\([^()]*\))*\))?/giy;function rt(e){let t=[];Ii.lastIndex=0;let n=0;for(;n<e.length;){Ii.lastIndex=n;let r=Ii.exec(e);if(!r||r.index!==n)return null;let o=r[0];if(n+=o.length,o==="*")t.push({kind:"universal",raw:o});else if(r[1]!==void 0)t.push({kind:"class",raw:o,name:fu(r[1])});else if(r[2]!==void 0)t.push({kind:"id",raw:o,name:fu(r[2])});else if(r[3]!==void 0)t.push({kind:"attr",raw:o,name:r[3],op:r[4]??null,value:r[4]?r[5]??r[6]??r[7]??"":null});else if(r[8]!==void 0)t.push({kind:"pseudoElement",raw:o,name:r[8].toLowerCase()});else if(r[9]!==void 0){let s=r[9].toLowerCase();/^(?:before|after|first-line|first-letter)$/.test(s)?t.push({kind:"pseudoElement",raw:o,name:s}):t.push({kind:"pseudo",raw:o,name:s,args:r[10]?r[10].slice(1,-1).trim():null})}else if(t.length===0)t.push({kind:"tag",raw:o,name:o.toLowerCase()});else return null}return t.length?t:null}function de(e){let t=Sb(e),n=[],r=0,o=0,s=null,i=!0,l=a=>{let c=t.slice(o,a).trim();if(!c)return!0;let u=rt(c);return u?(n.push({combinator:i?null:s??" ",simples:u}),i=!1,s=null,!0):!1};for(let a=0;a<t.length;a++){let c=t[a];if(c==="("||c==="[")r++;else if(c===")"||c==="]")r=Math.max(0,r-1);else if(r===0&&(c===">"||c==="+"||c==="~")){if(!l(a)||s)return null;s=c,o=a+1}else if(r===0&&/\s/.test(c)){if(!l(a))return null;o=a+1}}return!l(t.length)||s?null:n.length?n:null}function Mn(e){return e.map(t=>t.raw).join("")}function yr(e){return e.map((t,n)=>{let r=Mn(t.simples);return n===0?r:t.combinator===" "?` ${r}`:` ${t.combinator} ${r}`}).join("")}function oe(e){let t=[],n=0,r=0;for(let o=0;o<e.length;o++){let s=e[o];s==="("||s==="["?n++:s===")"||s==="]"?n=Math.max(0,n-1):s===","&&n===0&&(t.push(e.slice(r,o).trim()),r=o+1)}return t.push(e.slice(r).trim()),t.filter(Boolean)}var hu=e=>e.replace(/([a-z\d])([A-Z])/g,"$1-$2").split(/[^a-z0-9]+/i).filter(Boolean).map(t=>t.toLowerCase());function gu(e,t){let n=e.toLowerCase();return t.some(r=>n.includes(r.toLowerCase()))}function Ei(e,t){let n=t.excludeClassKeywords;if(!n?.length)return!1;let r=new Set(n.flatMap(o=>[o.toLowerCase(),`${o.toLowerCase()}s`]));return e.some(o=>o.kind==="class"&&hu(o.name).some(s=>r.has(s)))}var $b=new Set(["html","body"]),Cb=new Set(["svg","path","use","img","picture","video","i","em","strong","b","small","kbd","sup","sub"]);function Oo(e,t){for(let n of e)switch(n.kind){case"tag":if(t.elements.includes(n.name))return!0;break;case"class":if(gu(n.name,t.classKeywords))return!0;break;case"attr":if(n.name==="role"&&n.value!==null&&t.roles.includes(n.value.toLowerCase())||n.name==="data-component"&&n.value!==null&&gu(n.value,t.classKeywords))return!0;break;case"pseudo":if((n.name==="is"||n.name==="where")&&n.args){let r=oe(n.args).map(de);if(r.length>0&&r.every(o=>o!==null&&Oo(o[o.length-1].simples,t)))return!0}break;default:break}return!1}function yu(e){return e.some(n=>n.kind==="universal")?e.length===1:e.some(n=>n.kind==="tag"&&$b.has(n.name))?!0:!e.some(n=>n.kind==="tag"||n.kind==="class"||n.kind==="attr"||n.kind==="id"||n.kind==="pseudo"&&(n.name==="is"||n.name==="where"))}function bu(e){return e.some(t=>t.kind==="tag"&&Cb.has(t.name))}var _o=["hover","focus","active","selected","current","loading","disabled"],Rb={hover:"hover",hovered:"hover",focus:"focus",focused:"focus","focus-visible":"focus","focus-within":"focus",active:"selected",pressed:"selected",selected:"selected",checked:"selected",on:"selected",current:"current",loading:"loading",busy:"loading",pending:"loading",disabled:"disabled"},Ab={hover:"hover",focus:"focus","focus-visible":"focus","focus-within":"focus",active:"active",checked:"selected",disabled:"disabled"},Ib={disabled:"disabled","aria-disabled":"disabled","data-disabled":"disabled","aria-pressed":"selected","aria-selected":"selected","aria-expanded":"selected","aria-checked":"selected","data-active":"selected","data-selected":"selected","data-pressed":"selected","data-checked":"selected","data-highlighted":"selected","aria-current":"current","aria-busy":"loading","data-loading":"loading","data-busy":"loading","data-hover":"hover","data-hovered":"hover","data-focus":"focus","data-focused":"focus","data-focus-visible":"focus","data-open":"open","data-expanded":"open"},Eb=new Set(["active","selected","pressed","checked","loading","busy","disabled","open","current","hover","focus","show","expanded","collapsed"]),mu=e=>Rb[e.toLowerCase()]??e.toLowerCase(),Ot={hover:"__gs-hover",focus:"__gs-focus",active:"__gs-active"};function wu(e){if(e.kind==="pseudo"){let t=Ab[e.name];if(!t)return null;let n=Ot[t];return{state:t,marker:n?{classes:[n],attrs:{}}:{classes:[],attrs:{[e.name==="checked"?"checked":"disabled"]:""}}}}if(e.kind==="attr"){if(e.name==="data-state"&&e.value!==null)return{state:mu(e.value),marker:{classes:[],attrs:{"data-state":e.value}}};let t=Ib[e.name];if(!t||e.value!==null&&/^(?:false|off|0)$/i.test(e.value))return null;let n=e.value??(e.name.startsWith("aria-")?"true":"");return{state:t,marker:{classes:[],attrs:{[e.name]:n}}}}if(e.kind==="class"){let t=/^(?:is|has)-(.+)$/i.exec(e.name),n=t?t[1]:Eb.has(e.name.toLowerCase())?e.name:null;return n?{state:mu(n),marker:{classes:[e.name],attrs:{}}}:null}return null}function Tb(e){return e.kind!=="pseudo"||e.name!=="not"||!e.args?!1:oe(e.args).every(t=>{let n=rt(t);return n!==null&&n.every(r=>wu(r)!==null)})}function yt(e){let t=[],n=new Map;for(let s of e){if(Tb(s))continue;let i=wu(s);if(!i){t.push(s);continue}let l=n.get(i.state)??{classes:[],attrs:{}};for(let a of i.marker.classes)l.classes.includes(a)||l.classes.push(a);Object.assign(l.attrs,i.marker.attrs),n.set(i.state,l)}let r=[...n.keys()].sort((s,i)=>Le(s)-Le(i)||s.localeCompare(i)),o={classes:[],attrs:{}};for(let s of r){let i=n.get(s);o.classes.push(...i.classes),Object.assign(o.attrs,i.attrs)}return{identity:t,states:r,marker:o}}function Le(e){if(e==="default")return-1;let t=_o.indexOf(e);return t===-1?_o.length:t}function ot(e){return e.length?e.join(" + "):"default"}function Ti(e){return e.replace(/:hover\b/g,`.${Ot.hover}`).replace(/:focus(?:-visible|-within)?\b/g,`.${Ot.focus}`).replace(/:active\b/g,`.${Ot.active}`)}var _b=new Set(["background","background-color","background-image","background-position","background-size","background-repeat","background-clip","background-origin","background-attachment","color","border-color","border-top-color","border-right-color","border-bottom-color","border-left-color","outline","outline-color","outline-offset","box-shadow","filter","backdrop-filter","opacity","fill","stroke","caret-color","accent-color","text-decoration-color","mix-blend-mode"]),Ob=new Set(["width","min-width","max-width","flex","flex-grow","flex-shrink","flex-basis","align-self","justify-self","order","margin","margin-top","margin-right","margin-bottom","margin-left","margin-inline","margin-block","margin-inline-start","margin-inline-end","margin-block-start","margin-block-end","position","inset","inset-inline","inset-block","top","right","bottom","left","z-index","float","clear","grid-column","grid-row","grid-area","place-self"]),jb=new Set(["cursor","user-select","pointer-events","transition","transition-property","transition-duration","transition-timing-function","transition-delay","will-change","touch-action","-webkit-tap-highlight-color","appearance","-webkit-appearance","scroll-margin","scroll-margin-top"]);function br(e){let t=e.toLowerCase();return _b.has(t)?"paint":Ob.has(t)?"layout":jb.has(t)?"hint":"box"}var Mb={shadow:/^box-shadow$/,cursor:/^cursor$/,rounded:/^border(?:-[a-z]+)*-radius$/,radius:/^border(?:-[a-z]+)*-radius$/};function ku(e,t){let n=e.filter(s=>s.kind==="class");if(n.length!==1||e.length!==1)return!1;let r=hu(n[0].name)[0],o=r?Mb[r]:void 0;return o?t.length>0&&t.every(s=>o.test(s.property.toLowerCase())):!1}function wr(e){let t=[],n=/var\(\s*(--[\w-]+)\s*(,)?/g;for(let r of e.matchAll(n)){let o=null;if(r[2]){let s=1,i=r.index+r[0].length,l=i;for(;l<e.length;l++)if(e[l]==="(")s++;else if(e[l]===")"&&--s===0)break;o=e.slice(i,l).trim()}t.push({name:r[1],fallback:o,index:r.index})}return t}function Pb(e){let t="",n=0;for(;n<e.length;){let r=e.indexOf("var(",n);if(r<0)return t+e.slice(n);t+=e.slice(n,r);let o=0,s=r+3;for(;s<e.length;s++)if(e[s]==="(")o++;else if(e[s]===")"&&--o===0)break;n=s+1}return t}var Db=/(?<![\w#-])-?\d*\.?\d+(?:px|rem|em|vw|vh|vmin|vmax|ch|ex|%)/gi;function jo(e){let t=Pb(e);return new RegExp(hr.source,"i").test(t)?!0:[...t.matchAll(Db)].some(n=>parseFloat(n[0])!==0)}function xu(e,t){let n=pe(e),r=pe(t);return n||r?n===r:e.trim().toLowerCase()===t.trim().toLowerCase()}function Lb(e){let t=de(e);if(!t)return null;let n=o=>new Set(t[o].simples.flatMap(s=>s.kind==="class"?[s.name]:[])),r=[n(t.length-1)];for(let o=t.length-2;o>=0;o--){let s=t[o+1].combinator;(s===" "||s===">")&&r.push(n(o))}for(let o of[...r])for(let s of o){let i=s.match(/^(.+?)__/)?.[1];i&&r.push(new Set([i]))}return r}var Nb=(e,t)=>[...t].some(n=>e.startsWith(`${n}--`));function Fb(e,t,n){let r="no";for(let o of oe(e.selector)){let s=de(o);if(!s)return"possible";let i=s[s.length-1].simples,l=i.flatMap(u=>u.kind==="class"?[u.name]:[]),a=s.length>1||e.context!==""||i.some(u=>u.kind!=="class"&&u.kind!=="tag"),c="no";if(l.length?t.some(u=>l.every(p=>u.has(p)))?c=a?"possible":"proven":(t.some(u=>l.every(p=>u.has(p)||Nb(p,u)))||n.some(u=>l.every(p=>u.includes(p))&&t.some(p=>u.some(d=>p.has(d)))))&&(c="possible"):c="possible",c==="proven")return c;c==="possible"&&(r=c)}return r}function vu(e){let t=[];for(let n of e.matchAll(/\bclass(?:Name)?\s*=\s*\{?\s*["'`]([^"'`]+)["'`]/g)){let r=n[1].split(/\s+/).filter(o=>/^[\w-]+$/.test(o));r.length>1&&t.push(r)}return t}function Su(e,t,n){let r=new Map;for(let a of e)r.set(a.name,[...r.get(a.name)??[],a]);let o=()=>({covered:!0,tokens:[],through:[],failing:[],reason:null}),s=(a,c)=>{a.tokens.push(...c.tokens.filter(u=>!a.tokens.includes(u))),a.through.push(...c.through.filter(u=>!a.through.includes(u))),a.failing.push(...c.failing.filter(u=>!a.failing.includes(u))),c.covered||(a.covered=!1,a.reason??=c.reason)};function i(a,c,u){let p=o();for(let d of wr(a))s(p,l(d.name,d.fallback,c,u));return jo(a)&&(p.covered=!1,p.reason??="set to a hard-coded value"),p}function l(a,c,u,p){if(p.has(a))return{...o(),covered:!1,reason:"refers to itself"};let d=new Set([...p,a]),f=[],m=[];for(let h of r.get(a)??[]){let b=Fb(h,u,t);b==="proven"?f.push(h):b==="possible"&&m.push(h)}let g=n.get(a),S=o();f.length||(g!==void 0?S.tokens.push(a):c==null?(S.covered=!1,S.reason="no declaration is proven to apply here"):s(S,i(c,u,d)));for(let h of[...f,...m]){if(g!==void 0&&xu(h.value,g)){S.through.push(h),S.tokens.includes(a)||S.tokens.push(a);continue}let b=i(h.value,u,d);S.through.push(h),!b.covered&&(jo(h.value)||!b.failing.length)&&b.failing.unshift(h),s(S,b)}return S}return(a,c,u)=>{let p=o();if(u==null)return p.covered=n.has(a),p.covered?p.tokens.push(a):p.reason="read outside a stylesheet rule",p;for(let d of oe(u)){let f=Lb(d);if(!f){s(p,{...o(),covered:!1,reason:"selector Grum can't read"});continue}s(p,l(a,c,f,new Set))}return p}}var $u={color:"Color",typography:"Typography",spacing:"Spacing",radius:"Radius",shadow:"Shadows",other:"Other"},Ub=/\.(css|scss|sass|less|pcss)$/i,Gb=/^(?::root|html|body|:host)?(?:\[data-[\w-]+(?:[~|^$*]?=[^\]]*)?\]|\.(?:(?:light|dark)(?:-mode)?|theme-[\w-]+))*$/i;function Cu(e){let t=e.split(",").map(n=>n.trim());return t.length>0&&t.every(n=>n!==""&&Gb.test(n))}function en(e){return Ub.test(e)}function zb(...e){let t=e.map(r=>r??"").join("\0"),n=5381;for(let r=0;r<t.length;r++)n=(n*33^t.charCodeAt(r))>>>0;return n.toString(36)}function jt(e){let t=n=>(n??"").replace(/\s+/g," ").trim();return zb(e.file,t(e.selector),t(e.context),e.property.toLowerCase(),t(e.value).toLowerCase())}function _i(e){let t=e.evidence;return t?t.observed.length?"observed":t.definitions.some(n=>n.useCount>0)?"implemented":"defined":"unverified"}var Bb=200,Eu=/\.html?$/i,Wb=/\.(m?[jt]sx?|vue|svelte|astro)$/i,Vb=new Set(["area","base","br","col","embed","hr","img","input","link","meta","source","track","wbr"]),Hb=new Set(["script","style","textarea","title"]),qb=new Set(["li","p","option","tr","td","th","dt","dd"]),Mo=/\{\{|\$\{|<%|\{%/;function Jb(e){return`/${e}`.replace(/^\/(?:public|static|dist|site|docs)\//,"/").replace(/(^|\/)index\.html?$/i,"$1")}function Kb(e){let t=e.split(`
|
|
5
5
|
`),n={tag:"#document",id:null,classes:[],attrs:{},line:0,snippet:"",parent:null,children:[],dynamic:!1},r=[],o="",s=n,i=1,l=0,a=c=>{for(let u=l;u<c&&u<e.length;u++)e[u]===`
|
|
@@ -117,8 +117,8 @@ ${n}${o}`}var $s=(e,t)=>`If the Grum tools (grum_contract, grum_check, grum_requ
|
|
|
117
117
|
`))}return o}var q=class e extends Error{line;column;codeblock;constructor(t,n){let[r,o]=O$(n.toml,n.ptr),s=j$(n.toml,r,o);super(`Invalid TOML document: ${t}
|
|
118
118
|
|
|
119
119
|
${s}`,n),this.line=r,this.column=o,this.codeblock=s}static x(t,n,r){throw new e(t,{toml:n.s,ptr:r??n.p})}};function Us(e){let t=e.p,n=e.s.charCodeAt(e.p++),r=n,o=n===39,s=n===e.s.charCodeAt(e.p)&&n===e.s.charCodeAt(e.p+1);s&&((n=e.s.charCodeAt(e.p+=2))===10?e.p++:n===13&&e.s.charCodeAt(e.p+1)===10&&(e.p+=2));let i="",l=e.p,a=0;for(;e.p<e.s.length;e.p++)if(n=e.s.charCodeAt(e.p),s&&(n===10||n===13&&e.s.charCodeAt(e.p+1)===10))a=a&&3;else if(n<32&&n!==9||n===127)q.x("control characters are not allowed in strings",e);else if((!a||a===3)&&n===r&&(!s||e.s.charCodeAt(e.p+1)===r&&e.s.charCodeAt(e.p+2)===r)){if(s&&(e.s.charCodeAt(e.p+3)===r&&e.p++,e.s.charCodeAt(e.p+3)===r&&e.p++),!a){let c=e.s.slice(l,e.p);i=i?i+c:c}return e.p+=s?3:1,i}else if(!a)!o&&n===92&&(i+=e.s.slice(l,l=e.p),a=1);else if(a===1)if(n===120||n===117||n===85){let c=e.p++-1,u=0,p=n===120?2:n===117?4:8;for(let d=0;d<p;d++,e.p++){let f=e.s.charCodeAt(e.p),m=f>=48&&f<=57?f-48:f>=65&&f<=70?f-65+10:f>=97&&f<=102?f-97+10:-1;m<0&&q.x("invalid non-hex character in unicode escape",e),u=u<<4|m}(u<0||u>1114111||u>=55296&&u<=57343)&&q.x("invalid unicode escape",e,c),i+=String.fromCodePoint(u),l=e.p--,a=0}else s&&(n===32||n===9)?a=2:(n===98?i+="\b":n===116?i+=" ":n===110?i+=`
|
|
120
|
-
`:n===102?i+="\f":n===114?i+="\r":n===101?i+="\x1B":n===34?i+='"':n===92?i+="\\":q.x("unrecognised escape sequence",e),l=e.p+1,a=0);else n!==32&&n!==9&&(a===2&&q.x("invalid escape: only line-ending whitespace may be escaped",e,l),a=!o&&n===92?1:0,l=e.p);q.x("unfinished string",e,t)}var M$=/^(\d{4}-\d{2}-\d{2})?[Tt ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|z|[-+]\d{2}:\d{2})?$/i,Zr=class e extends Date{#t=!1;#n=!1;#e=null;constructor(t,n,r){let o=!0,s=!0,i="Z",l;if(typeof t=="string")if(n)e:{if(n<3){if(+t.slice(11,13)>23){t="";break e}n===2?(i=null,t+="Z"):(l=t.charCodeAt(t.length-1))!==90&&l!==122&&(i=t.slice(t.length-6)),r&&(t=t.slice(0,10)+"T"+t.slice(11))}else n===4&&(t=+t.slice(0,2)>23?"":`0000-01-01T${t}Z`);o=n!==4,s=n!==3}else{let a=t.match(M$);a?(a[1]||(o=!1,t=`0000-01-01T${t}`),s=!!a[2],s&&t[10]===" "&&(t=t.replace(" ","T")),a[2]&&+a[2]>23?t="":(i=a[3]||null,!i&&s&&(t+="Z"))):t=""}super(t),isNaN(this.getTime())||(this.#t=o,this.#n=s,this.#e=i)}isDateTime(){return this.#t&&this.#n}isLocal(){return!this.#t||!this.#n||!this.#e}isDate(){return this.#t&&!this.#n}isTime(){return this.#n&&!this.#t}isValid(){return this.#t||this.#n}toISOString(){let t=super.toISOString();if(this.isDate())return t.slice(0,10);if(this.isTime())return t.slice(11,23);if(this.#e===null)return t.slice(0,-1);if(this.#e==="Z"||this.#e==="z")return t;let n=+this.#e.slice(1,3)*60+ +this.#e.slice(4,6);return n=this.#e[0]==="-"?n:-n,new Date(this.getTime()-n*6e4).toISOString().slice(0,-1)+this.#e}static wrapAsOffsetDateTime(t,n="Z"){let r=new e(t);return r.#e=n,r}static wrapAsLocalDateTime(t){let n=new e(t);return n.#e=null,n}static wrapAsLocalDate(t){let n=new e(t);return n.#n=!1,n.#e=null,n}static wrapAsLocalTime(t){let n=new e(t);return n.#t=!1,n.#e=null,n}};function Ue(e,t=10){return t===16?e>47&&e<58||e>64&&e<71||e>96&&e<103:e>47&&e<48+t}function Ll(e,t){return e===32||e===9||e===10||e===13||t&&(e===t||e===44)||e===35}function Qr(e,t){let n=e.p,r=e.s.charCodeAt(e.p);if(r===91||r===123){e.d--||q.x("document contains excessively nested structures. aborting.",e);let o=r===91?Ag(e):Rg(e);return e.d++,o}return r===34||r===39?Us(e):r===116?((e.s.charCodeAt(++e.p)!==114||e.s.charCodeAt(++e.p)!==117||e.s.charCodeAt(++e.p)!==101)&&q.x("invalid value",e,n),e.p++,!0):r===102?((e.s.charCodeAt(++e.p)!==97||e.s.charCodeAt(++e.p)!==108||e.s.charCodeAt(++e.p)!==115||e.s.charCodeAt(++e.p)!==101)&&q.x("invalid value",e,n),e.p++,!1):r===43||r===45?Gs(e,e.p,e.s.charCodeAt(++e.p),44-r,t):e.s.charCodeAt(e.p+4)===45&&e.s.charCodeAt(e.p+7)===45?P$(e,r,t):e.s.charCodeAt(e.p+2)===58?D$(e,r,t):Gs(e,e.p,r,0,t)}function Gs(e,t,n,r,o){let s=n,i=0,l=!1;if(s===105)return(e.s.charCodeAt(++e.p)!==110||e.s.charCodeAt(++e.p)!==102)&&q.x("invalid value",e,t),e.p++,(r||1)/0;if(s===110)return(e.s.charCodeAt(++e.p)!==97||e.s.charCodeAt(++e.p)!==110)&&q.x("invalid value",e,t),e.p++,NaN;if(s===48){if(++e.p>=e.s.length||Ll(s=e.s.charCodeAt(e.p),o))return e.bi===!0?0n:0;if(!r){if(s===120)return Dl(e,t,16,o);if(s===98)return Dl(e,t,2,o);if(s===111)return Dl(e,t,8,o)}s===46?i=2:s===101||s===69?i=4:q.x("illegal leading zero",e,t)}else Ue(s)||q.x("invalid value",e,t);for(;++e.p<e.s.length&&(s=e.s.charCodeAt(e.p),!Ll(s,o));)i||(i=1),s===95?(i&1||q.x("illegal underscore",e),i+=11,l=!0):i===1&&s===46?i=2:(i===1||i===3)&&(s===101||s===69)?i=4:i===4&&(s===43||s===45)||(Ue(s)?i>9?i-=11:i&1||i++:q.x("illegal character in numeric literal",e));if(!i){let c=(n-48)*(r||1);return e.bi===!0?BigInt(c):c}i&1||q.x("unfinished numeric value",e,t);let a=e.s.slice(t,e.p);return l&&(a=a.replaceAll("_","")),i>1?parseFloat(a):Cg(e,a,10,t)}function Dl(e,t,n,r){let o,s=1;for(;++e.p<e.s.length&&(o=e.s.charCodeAt(e.p),!Ll(o,r));)o===95?(s&1&&q.x("illegal underscore",e),s=3):Ue(o,n)?s&1&&s--:q.x("illegal character in numeric literal",e);s&1&&q.x("unfinished numeric value",e);let i=e.s.slice(t+2,e.p);return s&&(i=i.replaceAll("_","")),Cg(e,i,n,t)}function Cg(e,t,n,r){if(e.bi!==!0)e:{let o=parseInt(t,n);if(!Number.isSafeInteger(o)){if(e.bi)break e;q.x("integer value cannot be represented losslessly",e,r)}return o}return BigInt(n===10?t:(n===2?"0b":n===8?"0o":"0x")+t)}function P$(e,t,n){let r=e.p++,o;if(!Ue(t)||!Ue(e.s.charCodeAt(e.p++))||!Ue(e.s.charCodeAt(e.p++))||!Ue(e.s.charCodeAt(e.p++)))return Gs(e,e.p=r,t,0,n);if(e.p+=5,Ue(e.s.charCodeAt(e.p++))||q.x("invalid date-time: date part is malformed",e,r),e.p>=e.s.length||((t=e.s.charCodeAt(e.p))!==32||(o=!0,!Ue(e.s.charCodeAt(e.p+1))))&&t!==84&&t!==116){let i=e.s.slice(r,e.p);return Yr(e,i,3,!1,r)}if(e.s.charCodeAt(e.p+=3)!==58&&q.x("invalid date-time: time part is malformed",e,r),e.s.charCodeAt(e.p+=3)===58&&(e.p+=3),e.s.charCodeAt(e.p)===46)for(;Ue(e.s.charCodeAt(++e.p)););if(t=e.s.charCodeAt(e.p)){if(t===90||t===122){let i=e.s.slice(r,++e.p);return Yr(e,i,1,o,r,"[+00:00]")}if(t===43||t===45){let i=e.s.slice(r,e.p+=6);return Yr(e,i,1,o,r,!e.ld&&"["+e.s.slice(e.p-6,e.p)+"]")}}let s=e.s.slice(r,e.p);return Yr(e,s,2,o,r)}function D$(e,t,n){let r=e.p;if(!Ue(t)||!Ue(e.s.charCodeAt(++e.p)))return Gs(e,--e.p,t,0,n);if(e.s.charCodeAt(e.p+=4)===58&&(e.p+=3),e.s.charCodeAt(e.p)===46)for(;Ue(e.s.charCodeAt(++e.p)););let o=e.s.slice(r,e.p);return Yr(e,o,4,!1,r)}function Yr(e,t,n,r,o,s){if(e.ld){let i=new Zr(t,n,r);return i.isValid()||q.x("invalid date",e,o),i}try{switch(s&&(t+=s),n){case 1:return Temporal.ZonedDateTime.from(t);case 2:return Temporal.PlainDateTime.from(t);case 3:return Temporal.PlainDate.from(t);case 4:return Temporal.PlainTime.from(t)}}catch(i){q.x(i instanceof Error?i.message:""+i,e,o)}}function L$(e){for(;e.p<e.s.length;e.p++){let t=e.s.charCodeAt(e.p);if(t===10)break;if(t===13&&e.s.charCodeAt(e.p+1)===10){e.p++;break}(t<32&&t!==9||t===127)&&q.x("control characters are not allowed in comments",e)}}function et(e,t,n){let r;for(;e.p<e.s.length;){for(;e.p<e.s.length&&((r=e.s.charCodeAt(e.p))===32||r===9||!t&&(r===10||r===13&&e.s.charCodeAt(e.p+1)===10));)e.p++;if(n||r!==35)break;L$(e)}}function zs(e,t=61){let n,r=0,o=[],s,i=e.s.charCodeAt(n=e.p);do{if(i===t)return r||q.x("unexpected end of key",e),r===1&&o.push(e.s.slice(s,e.p)),e.p++,o;i===46?(r||q.x("illegal empty bare key",e),r===1&&o.push(e.s.slice(s,e.p)),r=0):!r&&(i===34||i===39)?(i===e.s.charCodeAt(e.p+1)&&i===e.s.charCodeAt(e.p+2)&&q.x("illegal quoted key: multiline strings are not allowed",e),o.push(Us(e)),r=2,e.p--):i===32||i===9?r===1&&(o.push(e.s.slice(s,e.p)),r=2):r===2||i<48&&i!==45||i>57&&i<65||i>90&&i<97&&i!==95||i>122?q.x("illegal character in key",e):r||(r=1,s=e.p)}while(i=e.s.charCodeAt(++e.p));q.x("incomplete key-value: cannot find end of key",e,n)}function Rg(e){let t=e.p++,n=Object.create(null),r=new Set,o;for(;e.p<e.s.length;){if(et(e),(o=e.s.charCodeAt(e.p))===125)return e.p++,n;let s,i=n,l=!1,a=e.p,c=zs(e);for(let p=0;p<c.length;p++){p&&(i=l?i[s]:i[s]=Object.create(null)),s=c[p],(l=Object.hasOwn(i,s))&&(typeof i[s]!="object"||r.has(i[s]))&&q.x("trying to redefine an already defined value",e,a);let d=s==="__proto__";if(e.uk&&(d||s==="constructor")){i=e.uk!==1&&q.x("document contains an unsafe property",e,a);break}!l&&d&&Object.defineProperty(i,s,{enumerable:!0,configurable:!0,writable:!0})}l&&q.x("trying to redefine an already defined value",e,a),et(e,!0,!0);let u=Qr(e,125);if(i&&typeof(i[s]=u)=="object"&&r.add(u),et(e),(o=e.s.charCodeAt(e.p++))===125)return n;o!==44&&q.x("expected comma or end of structure",e,e.p-1)}q.x("unfinished table",e,t)}function Ag(e){let t=e.p++,n=[],r;for(;e.p<e.s.length;){if(et(e),(r=e.s.charCodeAt(e.p))===93)return e.p++,n;if(n.push(Qr(e,93)),et(e),(r=e.s.charCodeAt(e.p++))===93)return n;r!==44&&q.x("expected comma or end of structure",e,e.p-1)}q.x("unfinished array",e,t)}function Ig(e,t,n,r,o){let s=n,i=r,l,a=!1,c;for(let u=0;u<t.length;u++){if(u){if(s=a?s[l]:s[l]=Object.create(null),i=(c=i[l]).c,o===0&&(c.t===1||c.t===2))return null;if(c.t===2){let p=s.length-1;s=s[p],i=i[p].c}}if(l=t[u],(a=Object.hasOwn(s,l))&&i[l]?.t===0&&i[l]?.d)return null;if(!a){let p=l==="__proto__";if(e.uk&&(p||l==="constructor"))return!1;p&&(Object.defineProperty(s,l,{enumerable:!0,configurable:!0,writable:!0}),Object.defineProperty(i,l,{enumerable:!0,configurable:!0,writable:!0})),i[l]={t:u<t.length-1&&o===2?3:o,d:!1,i:0,c:Object.create(null)}}}if(c=i[l],c.t!==o&&!(o===1&&c.t===3)||(o===2&&(c.d||(c.d=!0,s[l]=[]),s[l].push(s=Object.create(null)),c.c[c.i++]=c={t:1,d:!1,i:0,c:Object.create(null)}),c.d))return null;if(c.d=!0,o===1)s=a?s[l]:s[l]=Object.create(null);else if(o===0&&a)return null;return[l,s,c.c]}function Eg(e,t,n){(t===null||e.uk===2)&&q.x(t===null?"trying to redefine an already defined table or value":"document contains an unsafe property",e,n)}function Bs(e,t={}){let n={s:e,p:0,d:t.maxDepth??1e3,bi:t.integersAsBigInt??!1,ld:t.useLegacyDate??!0,uk:t.unsafeKeyBehaviour==="throw"?2:t.unsafeKeyBehaviour==="drop"?1:0},r=Object.create(null),o=Object.create(null),s,i=!1,l=r,a=o;for(e.charCodeAt(0)===65279&&n.p++,et(n);n.p<e.length;){if(e.charCodeAt(n.p)===91){let c=e.charCodeAt(++n.p)===91;s=n.p+=+c,i=!1;let u=zs(n,93);c&&(e.charCodeAt(n.p)!==93&&q.x("expected end of table array declaration",n),n.p++);let p=Ig(n,u,r,o,c?2:1);p?(a=p[2],l=p[1]):(Eg(n,p,s),i=!0)}else{s=n.p;let c=zs(n),u=Ig(n,c,l,a,0);!u&&!i&&Eg(n,u,s),et(n,!0,!0);let p=Qr(n,void 0);u&&!i&&(u[1][u[0]]=p)}et(n,!0),n.p<e.length&&(s=e.charCodeAt(n.p))!==10&&(s!==13||e.charCodeAt(n.p+1)!==10)&&q.x("each key-value declaration must be followed by an end-of-line",n),et(n)}return r}var e_=!!"".isWellFormed;var rr="codex-hooks-seen.json",Nl=`open Settings > Hooks, open this project, and allow Grum's 3 commands (they say "need review" and run grum.mjs hook ...; in the Codex terminal, run /hooks). That list is a different screen from MCP servers. Until you allow them, Grum cannot check when Codex stops or block a commit.`,Fl=()=>["Two steps are left in Codex. Grum will not appear in MCP or Hooks until you do them:"," 1. Trust this folder when Codex asks. Then in Settings > MCP servers, turn on grum. If the list uses this folder's name, pick the entry whose command runs grum.mjs.",` 2. Open Settings > Hooks, open this project, and allow Grum's 3 commands (they say "need review" and run grum.mjs hook ...; in the terminal app, run /hooks). This is a different screen from MCP.`,"Without step 2, Grum cannot check when Codex stops or block a commit."].join(`
|
|
121
|
-
`);function Tg(e){return!e.installed||e.seen||!/codex/i.test(e.client??"")?null:`Grum
|
|
120
|
+
`:n===102?i+="\f":n===114?i+="\r":n===101?i+="\x1B":n===34?i+='"':n===92?i+="\\":q.x("unrecognised escape sequence",e),l=e.p+1,a=0);else n!==32&&n!==9&&(a===2&&q.x("invalid escape: only line-ending whitespace may be escaped",e,l),a=!o&&n===92?1:0,l=e.p);q.x("unfinished string",e,t)}var M$=/^(\d{4}-\d{2}-\d{2})?[Tt ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|z|[-+]\d{2}:\d{2})?$/i,Zr=class e extends Date{#t=!1;#n=!1;#e=null;constructor(t,n,r){let o=!0,s=!0,i="Z",l;if(typeof t=="string")if(n)e:{if(n<3){if(+t.slice(11,13)>23){t="";break e}n===2?(i=null,t+="Z"):(l=t.charCodeAt(t.length-1))!==90&&l!==122&&(i=t.slice(t.length-6)),r&&(t=t.slice(0,10)+"T"+t.slice(11))}else n===4&&(t=+t.slice(0,2)>23?"":`0000-01-01T${t}Z`);o=n!==4,s=n!==3}else{let a=t.match(M$);a?(a[1]||(o=!1,t=`0000-01-01T${t}`),s=!!a[2],s&&t[10]===" "&&(t=t.replace(" ","T")),a[2]&&+a[2]>23?t="":(i=a[3]||null,!i&&s&&(t+="Z"))):t=""}super(t),isNaN(this.getTime())||(this.#t=o,this.#n=s,this.#e=i)}isDateTime(){return this.#t&&this.#n}isLocal(){return!this.#t||!this.#n||!this.#e}isDate(){return this.#t&&!this.#n}isTime(){return this.#n&&!this.#t}isValid(){return this.#t||this.#n}toISOString(){let t=super.toISOString();if(this.isDate())return t.slice(0,10);if(this.isTime())return t.slice(11,23);if(this.#e===null)return t.slice(0,-1);if(this.#e==="Z"||this.#e==="z")return t;let n=+this.#e.slice(1,3)*60+ +this.#e.slice(4,6);return n=this.#e[0]==="-"?n:-n,new Date(this.getTime()-n*6e4).toISOString().slice(0,-1)+this.#e}static wrapAsOffsetDateTime(t,n="Z"){let r=new e(t);return r.#e=n,r}static wrapAsLocalDateTime(t){let n=new e(t);return n.#e=null,n}static wrapAsLocalDate(t){let n=new e(t);return n.#n=!1,n.#e=null,n}static wrapAsLocalTime(t){let n=new e(t);return n.#t=!1,n.#e=null,n}};function Ue(e,t=10){return t===16?e>47&&e<58||e>64&&e<71||e>96&&e<103:e>47&&e<48+t}function Ll(e,t){return e===32||e===9||e===10||e===13||t&&(e===t||e===44)||e===35}function Qr(e,t){let n=e.p,r=e.s.charCodeAt(e.p);if(r===91||r===123){e.d--||q.x("document contains excessively nested structures. aborting.",e);let o=r===91?Ag(e):Rg(e);return e.d++,o}return r===34||r===39?Us(e):r===116?((e.s.charCodeAt(++e.p)!==114||e.s.charCodeAt(++e.p)!==117||e.s.charCodeAt(++e.p)!==101)&&q.x("invalid value",e,n),e.p++,!0):r===102?((e.s.charCodeAt(++e.p)!==97||e.s.charCodeAt(++e.p)!==108||e.s.charCodeAt(++e.p)!==115||e.s.charCodeAt(++e.p)!==101)&&q.x("invalid value",e,n),e.p++,!1):r===43||r===45?Gs(e,e.p,e.s.charCodeAt(++e.p),44-r,t):e.s.charCodeAt(e.p+4)===45&&e.s.charCodeAt(e.p+7)===45?P$(e,r,t):e.s.charCodeAt(e.p+2)===58?D$(e,r,t):Gs(e,e.p,r,0,t)}function Gs(e,t,n,r,o){let s=n,i=0,l=!1;if(s===105)return(e.s.charCodeAt(++e.p)!==110||e.s.charCodeAt(++e.p)!==102)&&q.x("invalid value",e,t),e.p++,(r||1)/0;if(s===110)return(e.s.charCodeAt(++e.p)!==97||e.s.charCodeAt(++e.p)!==110)&&q.x("invalid value",e,t),e.p++,NaN;if(s===48){if(++e.p>=e.s.length||Ll(s=e.s.charCodeAt(e.p),o))return e.bi===!0?0n:0;if(!r){if(s===120)return Dl(e,t,16,o);if(s===98)return Dl(e,t,2,o);if(s===111)return Dl(e,t,8,o)}s===46?i=2:s===101||s===69?i=4:q.x("illegal leading zero",e,t)}else Ue(s)||q.x("invalid value",e,t);for(;++e.p<e.s.length&&(s=e.s.charCodeAt(e.p),!Ll(s,o));)i||(i=1),s===95?(i&1||q.x("illegal underscore",e),i+=11,l=!0):i===1&&s===46?i=2:(i===1||i===3)&&(s===101||s===69)?i=4:i===4&&(s===43||s===45)||(Ue(s)?i>9?i-=11:i&1||i++:q.x("illegal character in numeric literal",e));if(!i){let c=(n-48)*(r||1);return e.bi===!0?BigInt(c):c}i&1||q.x("unfinished numeric value",e,t);let a=e.s.slice(t,e.p);return l&&(a=a.replaceAll("_","")),i>1?parseFloat(a):Cg(e,a,10,t)}function Dl(e,t,n,r){let o,s=1;for(;++e.p<e.s.length&&(o=e.s.charCodeAt(e.p),!Ll(o,r));)o===95?(s&1&&q.x("illegal underscore",e),s=3):Ue(o,n)?s&1&&s--:q.x("illegal character in numeric literal",e);s&1&&q.x("unfinished numeric value",e);let i=e.s.slice(t+2,e.p);return s&&(i=i.replaceAll("_","")),Cg(e,i,n,t)}function Cg(e,t,n,r){if(e.bi!==!0)e:{let o=parseInt(t,n);if(!Number.isSafeInteger(o)){if(e.bi)break e;q.x("integer value cannot be represented losslessly",e,r)}return o}return BigInt(n===10?t:(n===2?"0b":n===8?"0o":"0x")+t)}function P$(e,t,n){let r=e.p++,o;if(!Ue(t)||!Ue(e.s.charCodeAt(e.p++))||!Ue(e.s.charCodeAt(e.p++))||!Ue(e.s.charCodeAt(e.p++)))return Gs(e,e.p=r,t,0,n);if(e.p+=5,Ue(e.s.charCodeAt(e.p++))||q.x("invalid date-time: date part is malformed",e,r),e.p>=e.s.length||((t=e.s.charCodeAt(e.p))!==32||(o=!0,!Ue(e.s.charCodeAt(e.p+1))))&&t!==84&&t!==116){let i=e.s.slice(r,e.p);return Yr(e,i,3,!1,r)}if(e.s.charCodeAt(e.p+=3)!==58&&q.x("invalid date-time: time part is malformed",e,r),e.s.charCodeAt(e.p+=3)===58&&(e.p+=3),e.s.charCodeAt(e.p)===46)for(;Ue(e.s.charCodeAt(++e.p)););if(t=e.s.charCodeAt(e.p)){if(t===90||t===122){let i=e.s.slice(r,++e.p);return Yr(e,i,1,o,r,"[+00:00]")}if(t===43||t===45){let i=e.s.slice(r,e.p+=6);return Yr(e,i,1,o,r,!e.ld&&"["+e.s.slice(e.p-6,e.p)+"]")}}let s=e.s.slice(r,e.p);return Yr(e,s,2,o,r)}function D$(e,t,n){let r=e.p;if(!Ue(t)||!Ue(e.s.charCodeAt(++e.p)))return Gs(e,--e.p,t,0,n);if(e.s.charCodeAt(e.p+=4)===58&&(e.p+=3),e.s.charCodeAt(e.p)===46)for(;Ue(e.s.charCodeAt(++e.p)););let o=e.s.slice(r,e.p);return Yr(e,o,4,!1,r)}function Yr(e,t,n,r,o,s){if(e.ld){let i=new Zr(t,n,r);return i.isValid()||q.x("invalid date",e,o),i}try{switch(s&&(t+=s),n){case 1:return Temporal.ZonedDateTime.from(t);case 2:return Temporal.PlainDateTime.from(t);case 3:return Temporal.PlainDate.from(t);case 4:return Temporal.PlainTime.from(t)}}catch(i){q.x(i instanceof Error?i.message:""+i,e,o)}}function L$(e){for(;e.p<e.s.length;e.p++){let t=e.s.charCodeAt(e.p);if(t===10)break;if(t===13&&e.s.charCodeAt(e.p+1)===10){e.p++;break}(t<32&&t!==9||t===127)&&q.x("control characters are not allowed in comments",e)}}function et(e,t,n){let r;for(;e.p<e.s.length;){for(;e.p<e.s.length&&((r=e.s.charCodeAt(e.p))===32||r===9||!t&&(r===10||r===13&&e.s.charCodeAt(e.p+1)===10));)e.p++;if(n||r!==35)break;L$(e)}}function zs(e,t=61){let n,r=0,o=[],s,i=e.s.charCodeAt(n=e.p);do{if(i===t)return r||q.x("unexpected end of key",e),r===1&&o.push(e.s.slice(s,e.p)),e.p++,o;i===46?(r||q.x("illegal empty bare key",e),r===1&&o.push(e.s.slice(s,e.p)),r=0):!r&&(i===34||i===39)?(i===e.s.charCodeAt(e.p+1)&&i===e.s.charCodeAt(e.p+2)&&q.x("illegal quoted key: multiline strings are not allowed",e),o.push(Us(e)),r=2,e.p--):i===32||i===9?r===1&&(o.push(e.s.slice(s,e.p)),r=2):r===2||i<48&&i!==45||i>57&&i<65||i>90&&i<97&&i!==95||i>122?q.x("illegal character in key",e):r||(r=1,s=e.p)}while(i=e.s.charCodeAt(++e.p));q.x("incomplete key-value: cannot find end of key",e,n)}function Rg(e){let t=e.p++,n=Object.create(null),r=new Set,o;for(;e.p<e.s.length;){if(et(e),(o=e.s.charCodeAt(e.p))===125)return e.p++,n;let s,i=n,l=!1,a=e.p,c=zs(e);for(let p=0;p<c.length;p++){p&&(i=l?i[s]:i[s]=Object.create(null)),s=c[p],(l=Object.hasOwn(i,s))&&(typeof i[s]!="object"||r.has(i[s]))&&q.x("trying to redefine an already defined value",e,a);let d=s==="__proto__";if(e.uk&&(d||s==="constructor")){i=e.uk!==1&&q.x("document contains an unsafe property",e,a);break}!l&&d&&Object.defineProperty(i,s,{enumerable:!0,configurable:!0,writable:!0})}l&&q.x("trying to redefine an already defined value",e,a),et(e,!0,!0);let u=Qr(e,125);if(i&&typeof(i[s]=u)=="object"&&r.add(u),et(e),(o=e.s.charCodeAt(e.p++))===125)return n;o!==44&&q.x("expected comma or end of structure",e,e.p-1)}q.x("unfinished table",e,t)}function Ag(e){let t=e.p++,n=[],r;for(;e.p<e.s.length;){if(et(e),(r=e.s.charCodeAt(e.p))===93)return e.p++,n;if(n.push(Qr(e,93)),et(e),(r=e.s.charCodeAt(e.p++))===93)return n;r!==44&&q.x("expected comma or end of structure",e,e.p-1)}q.x("unfinished array",e,t)}function Ig(e,t,n,r,o){let s=n,i=r,l,a=!1,c;for(let u=0;u<t.length;u++){if(u){if(s=a?s[l]:s[l]=Object.create(null),i=(c=i[l]).c,o===0&&(c.t===1||c.t===2))return null;if(c.t===2){let p=s.length-1;s=s[p],i=i[p].c}}if(l=t[u],(a=Object.hasOwn(s,l))&&i[l]?.t===0&&i[l]?.d)return null;if(!a){let p=l==="__proto__";if(e.uk&&(p||l==="constructor"))return!1;p&&(Object.defineProperty(s,l,{enumerable:!0,configurable:!0,writable:!0}),Object.defineProperty(i,l,{enumerable:!0,configurable:!0,writable:!0})),i[l]={t:u<t.length-1&&o===2?3:o,d:!1,i:0,c:Object.create(null)}}}if(c=i[l],c.t!==o&&!(o===1&&c.t===3)||(o===2&&(c.d||(c.d=!0,s[l]=[]),s[l].push(s=Object.create(null)),c.c[c.i++]=c={t:1,d:!1,i:0,c:Object.create(null)}),c.d))return null;if(c.d=!0,o===1)s=a?s[l]:s[l]=Object.create(null);else if(o===0&&a)return null;return[l,s,c.c]}function Eg(e,t,n){(t===null||e.uk===2)&&q.x(t===null?"trying to redefine an already defined table or value":"document contains an unsafe property",e,n)}function Bs(e,t={}){let n={s:e,p:0,d:t.maxDepth??1e3,bi:t.integersAsBigInt??!1,ld:t.useLegacyDate??!0,uk:t.unsafeKeyBehaviour==="throw"?2:t.unsafeKeyBehaviour==="drop"?1:0},r=Object.create(null),o=Object.create(null),s,i=!1,l=r,a=o;for(e.charCodeAt(0)===65279&&n.p++,et(n);n.p<e.length;){if(e.charCodeAt(n.p)===91){let c=e.charCodeAt(++n.p)===91;s=n.p+=+c,i=!1;let u=zs(n,93);c&&(e.charCodeAt(n.p)!==93&&q.x("expected end of table array declaration",n),n.p++);let p=Ig(n,u,r,o,c?2:1);p?(a=p[2],l=p[1]):(Eg(n,p,s),i=!0)}else{s=n.p;let c=zs(n),u=Ig(n,c,l,a,0);!u&&!i&&Eg(n,u,s),et(n,!0,!0);let p=Qr(n,void 0);u&&!i&&(u[1][u[0]]=p)}et(n,!0),n.p<e.length&&(s=e.charCodeAt(n.p))!==10&&(s!==13||e.charCodeAt(n.p+1)!==10)&&q.x("each key-value declaration must be followed by an end-of-line",n),et(n)}return r}var e_=!!"".isWellFormed;var rr="codex-hooks-seen.json",Nl=`if any of Grum's 3 commands (they say "need review" and run grum.mjs hook ...) are off, open Settings > Hooks for this project and allow them; this is separate from MCP servers. In the Codex terminal, run /hooks to review the list.`,Fl=()=>["Two steps are left in Codex. Grum will not appear in MCP or Hooks until you do them:"," 1. Trust this folder when Codex asks. Then in Settings > MCP servers, turn on grum. If the list uses this folder's name, pick the entry whose command runs grum.mjs.",` 2. Open Settings > Hooks for this project and check Grum's 3 commands (they say "need review" and run grum.mjs hook ...). Turn on any that are off; this is separate from MCP. In the terminal app, run /hooks to review the list.`,"Without step 2, Grum cannot check when Codex stops or block a commit."].join(`
|
|
121
|
+
`);function Tg(e){return!e.installed||e.seen||!/codex/i.test(e.client??"")?null:`Grum has not confirmed a successful Codex hook run in this project. This does not reveal whether the hooks are enabled. In the first sentence, say that hook status is unconfirmed; if the user says they are enabled, do not ask them to enable them again\u2014check the latest hook run for a command error. If their status is unknown, tell them to ${Nl} Then carry on.`}var ut=".codex/hooks.json",St=".codex/config.toml",qt="AGENTS.md",N$={PostToolUse:{matcher:"apply_patch|Edit|Write",name:"codex-post-tool",timeout:60},PreToolUse:{matcher:"Bash",name:"codex-pre-tool",timeout:120},Stop:{name:"codex-stop",timeout:120}},F$=/(^|[\s/])(?:@grum-os\/cli(?:@[^\s]+)?|grum(?:\.m?js)?)\s+hook\s+codex-(?:post-tool|pre-tool|stop)\s*$/,U$=e=>typeof e=="string"&&F$.test(e),_g={file:ut,hooks:N$,isGrum:U$};function Og(e,t){let n=bn(e,ut);if(!n.ok)return n;let r=Is(n.value,t,_g);return r.ok?!r.changed&&e!==null&&e.trim()?{ok:!0,text:e,changed:!1}:{ok:!0,text:er(r.root),changed:!0}:r}function Ul(e){if(e===null)return{ok:!0,text:"",changed:!1};let t=bn(e,ut);if(!t.ok)return t;let n=Kr(t.value,_g);return n.ok?n.changed?{ok:!0,text:Object.keys(n.root).length?er(n.root):"",changed:!0}:{ok:!0,text:e,changed:!1}:n}var G$="# BEGIN grum: written by `grum init codex`",z$="# END grum",eo=/(?:\r?\n)?^# BEGIN grum:[^\n]*\n[\s\S]*?^# END grum[^\n]*\n?/m;function B$(e){let[t,...n]=e.trim().split(/\s+/);return[G$,"[mcp_servers.grum]",`command = ${JSON.stringify(t)}`,`args = [${[...n,"mcp"].map(r=>JSON.stringify(r)).join(", ")}]`,z$,""].join(`
|
|
122
122
|
`)}function jg(e){try{Bs(e)}catch(t){let n=t instanceof Error?t.message.split(`
|
|
123
123
|
`)[0]:String(t);return{ok:!1,error:`${St} is not valid TOML (${n}); fix it or move it aside, then run init again.`}}return{ok:!0,text:e.replace(eo,""),had:eo.test(e)}}function Mg(e,t){let n=B$(t);if(e===null||!e.trim())return{ok:!0,text:n,changed:!0};let r=jg(e);if(!r.ok)return r;if(e.match(eo)?.[0].replace(/^\r?\n/,"")===n)return{ok:!0,text:e,changed:!1};let s=r.had?e.replace(eo,l=>`${l.startsWith(`
|
|
124
124
|
`)||l.startsWith(`\r
|
|
@@ -138,9 +138,9 @@ ${zg(s,n.cli)}
|
|
|
138
138
|
${Bg(n,!1,!1,s)}`}}var Yl="Grum refreshes review decisions before each MCP check or review-status lookup. If it cannot refresh, current review statuses and blocking findings are unknown; report the error instead of a cached decision.",Jl={how:"Shared MCP handlers refresh Grum's contract and review decisions on every grum_check and grum_review_status call; a failed refresh returns a tool error."},Kl={how:"Shared MCP tool `grum_add_to_contract` and `grum add-token`, offered when `.grum/contract.json` `project.role` is `owner`. These add tokens, or whole local button implementations with category button / `grum add-button`. Button additions keep existing approved specs. Inputs use review."},Xl={how:"Shared MCP tool `grum_snooze_review` and `grum snooze`, only for existing drift keys from a current check. Before any drift exists, deferring leaves code unchanged without calling snooze. This pauses questions, not scheduled notifications. The stop hook skips snoozed files. Git commit and push blocking does not."},Zl={how:"Shared `grum setup --agent --json` and MCP `grum_setup`: while the first scan runs, `wait-for-scan` carries its percent, step, label, elapsed time, heartbeat age, progress age, liveness and a retryAfterSeconds (10 working, 30 quiet). Each call returns at once; the agent reports one line and checks back later. A failed or stopped scan is `blocked` with no automatic restart."};var qg={cursor:{label:"Cursor",hooks:{"after-file-edit":"after-file-edit",stop:"stop","before-shell":"before-shell"},guidance:{file:Fe,text:wn,refresh:(e,t)=>e!==null&&tr(e)&&e!==wn(t)?wn(t):null},capabilities:{editTracking:{how:"`afterFileEdit` hook, with Cursor's `edits`"},stopFollowUp:{how:"`stop` hook, `followup_message`"},gitBlock:{how:"`beforeShellExecution` hook, `permission: deny`"},tools:{how:"`.cursor/mcp.json`"},reviewDecisions:Jl,guidance:{how:"`.cursor/rules/grum.mdc`, the whole file"},setup:{how:"`grum init cursor`; `grum setup` when `.cursor/` exists"},notOnWarning:{how:"Cursor adds the MCP server switched off; hooks and the rule tell the user to switch it on, read from `~/.cursor/projects/<path>/mcps/`"},shellCapture:{how:"`CURSOR_SANDBOX` set: `sandboxCapture` sends the agent to `grum_request_review`"},handoff:{how:"`grum check --cursor`, Cursor's prompt deeplink"},sandboxChrome:{how:"`beforeShellExecution` sends `sandbox: true` for a sandboxed command; `sandboxChromeDecision` denies one that starts Chrome"},addToContract:Kl,snoozeReview:Xl,scanProgress:Zl}},codex:{label:"Codex",hooks:{"codex-post-tool":"after-file-edit","codex-stop":"stop","codex-pre-tool":"before-shell"},guidance:{file:qt,text:e=>to(null,e).text,refresh:(e,t)=>{if(e===null||!Ws(e).changed)return null;let n=to(e,t);return n.changed?n.text:null}},capabilities:{editTracking:{how:"`PostToolUse` on `apply_patch|Edit|Write`; a patch's hunks rebuild the pre-edit text"},stopFollowUp:{how:'`Stop` hook, `decision: "block"`'},gitBlock:{how:"`PreToolUse` on `Bash`, `permissionDecision: deny`"},tools:{how:"`.codex/config.toml`, a marked `mcp_servers.grum` block"},reviewDecisions:Jl,guidance:{how:"a marked block in `AGENTS.md`"},setup:{how:"`grum init codex`; `grum setup` when `.codex/` exists"},notOnWarning:{how:"until a Codex hook has run, tool results carry a `notice` to allow the hooks (`codexHooksNotice`); setup writes Codex files before the scan finishes and tells the person to trust the folder, then enable grum in MCP and Hooks"},shellCapture:{how:"`CODEX_SANDBOX` set (Seatbelt, like Cursor's: `open` can't find Chrome there): `sandboxCapture` sends the agent to `grum_request_review`"},handoff:{no:"Not built: `--cursor` uses Cursor's prompt deeplink, and a Codex equivalent hasn't been looked into."},sandboxChrome:{no:"Codex's `PreToolUse` input doesn't say whether the command runs in its sandbox, so the hook can't deny only the run that would crash."},addToContract:Kl,snoozeReview:Xl,scanProgress:Zl}},claude:{label:"Claude Code",hooks:{"claude-post-tool":"after-file-edit","claude-stop":"stop","claude-pre-tool":"before-shell"},guidance:{file:kn,text:e=>Xr(null,e).text,refresh:(e,t)=>{if(e===null||!Fs(e).changed)return null;let n=Xr(e,t);return n.changed?n.text:null}},capabilities:{editTracking:{how:"`PostToolUse` on `Edit|Write|MultiEdit`; `old_string`/`new_string` (or `edits`) rebuild the pre-edit text"},stopFollowUp:{how:'`Stop` hook, `decision: "block"`'},gitBlock:{how:"`PreToolUse` on `Bash`, `permissionDecision: deny`"},tools:{how:"`.mcp.json`, approved in `.claude/settings.json` (`enabledMcpjsonServers`)"},reviewDecisions:Jl,guidance:{how:"a marked block in `CLAUDE.md`"},setup:{how:"`grum init claude`; `grum setup` when `.claude/` exists"},notOnWarning:{how:"doctor reads `~/.claude.json` for the project's trust and the server's approval (`claudeToolsState`); the guidance and hook text tell the user to trust the folder and approve `grum` when the tools are missing"},shellCapture:{no:"Not checked yet: Claude Code's optional Bash sandbox isn't detected, so whether a shell `grum request` can open a browser there is unknown."},handoff:{no:"Not built: `--cursor` uses Cursor's prompt deeplink, and a Claude Code equivalent hasn't been looked into."},sandboxChrome:{no:"Claude Code's Bash sandbox is optional and isn't detected (see shellCapture)."},addToContract:Kl,snoozeReview:Xl,scanProgress:Zl}}};function Jg(e){for(let[t,n]of Object.entries(qg))if(typeof e=="string"&&Object.hasOwn(n.hooks,e))return{agent:t,op:n.hooks[e]};return null}function Kg(e,t){return Object.values(qg).flatMap(({guidance:n})=>{let r=n.refresh(e(n.file),t);return r===null?[]:[{file:n.file,text:r}]})}var X$="2025-06-18",Jt=e=>`Check before you finish. ${As(e)}`,Z$=e=>({grum_contract:`Grum design contract: this repository's design contract as reviewed in Grum: its color, typography, spacing and radius tokens, its buttons (components, looks and how to select them, and the written rules), its approved text inputs, and review decisions. Read it before writing or restyling UI, including any button or input. ${Jt(e)}`,grum_check:`Grum check: check the working tree against the design contract and list new drift in code with a one-line fix each. It reads source files, not the live site. Drift already on the default branch isn't listed. Run it before you stop. ${Yl} ${Jt(e)}`,grum_request_review:`Grum review request: ask the project's owners to allow drift items. Call this only after the user chooses to keep the style and request review. Send all drift keys from grum_check that render on the same page, the user's reason and the page URL; Grum captures that page once for the reviewer. A single key remains supported. Before calling it, tell the user in one line: if that page needs a sign-in, a Grum page opens in their Chrome and they finish the capture there; this call waits for them (up to 10 minutes). ${Jt(e)}`,grum_add_to_contract:`Grum add to contract: add a token or whole button to the design contract. Call this only after an owner chooses to add the pattern, and only with the token or button name they confirmed. Send the drift key from grum_check, the name, category and page URL. Tokens also need value. For a button use category button; Grum reconstructs and saves the local implementation as another approved button, preserving the originals. Then use that token in the code. ${qr} ${Jt(e)}`,grum_snooze_review:`Grum snooze: pause the off-contract question for the files these drift keys touch, on the current branch. Call this only after the user chooses to pause questions about existing drift keys from grum_check. With no drift, leave a deferred change unimplemented and do not call this tool. It does not schedule a reminder or notification. Grum check still lists the drift, and a snooze does not unblock a commit or push. ${Jt(e)}`,grum_review_status:`Grum review status: review requested, allowed or rejected (with the reviewer's note) for this project's drift. Pass a key to read one. ${Yl} ${Jt(e)}`,grum_setup:`Grum setup: link this repository and configure the agent the user is using. Pass the same inputs as \`${e} setup --agent <cursor|codex|claude|generic> --json\`. Follow the JSON. Do not ask the user to open a terminal. A call without --create never creates a workspace. ${Jt(e)}`,grum_doctor:`Grum doctor: readiness for this repository. Changes nothing. Pass agent cursor, codex, claude or generic. Follow the JSON actions, then check again. Unknown checks are not ready. ${Jt(e)}`}),xn=e=>typeof e=="string"?e:"";function Xg(e,t){let n=Z$(t),r={type:"object",properties:{},additionalProperties:!1};return[{name:"grum_contract",description:n.grum_contract,inputSchema:r,run:()=>e.contract()},{name:"grum_check",description:n.grum_check,inputSchema:{type:"object",properties:{paths:{type:"array",items:{type:"string"},description:"Only report drift in these files (relative to the repository root). Omit to check everything."}},additionalProperties:!1},run:o=>e.check(Array.isArray(o.paths)?o.paths.filter(s=>typeof s=="string"):[])},{name:"grum_request_review",description:n.grum_request_review,inputSchema:{type:"object",properties:{key:{type:"string",description:"One drift key from grum_check. Use keys for a page set."},keys:{type:"array",items:{type:"string"},minItems:1,description:"All drift keys from grum_check that render on this page. They are captured together."},reason:{type:"string",description:"Why the contract has no token for this, in the user's words."},url:{type:"string",description:"The page where the element renders, for example http://localhost:3200/pricing. Grum captures how it looks and sends the picture. Needed when the repo's dev server port isn't in package.json."}},required:["reason"],additionalProperties:!1},run:o=>e.request(Array.isArray(o.keys)?o.keys.filter(s=>typeof s=="string"):xn(o.key)?[xn(o.key)]:[],xn(o.reason),xn(o.url))},{name:"grum_add_to_contract",description:n.grum_add_to_contract,inputSchema:{type:"object",properties:{key:{type:"string",description:"The current token or button drift key from grum_check."},name:{type:"string",description:"The name the user confirmed. Token names start with --; button names are at most 40 characters."},category:{type:"string",enum:["color","typography","spacing","radius","other","button"]},value:{type:"string",description:"The token's value."},url:{type:"string",description:"The page where the element renders."}},required:["key","name","category","url"],allOf:[{if:{properties:{category:{const:"button"}}},then:{},else:{required:["value"]}}],additionalProperties:!1},run:o=>e.addToContract(o)},{name:"grum_snooze_review",description:n.grum_snooze_review,inputSchema:{type:"object",properties:{key:{type:"string",minLength:1,description:"One drift key from grum_check. Use keys for several."},keys:{type:"array",items:{type:"string",minLength:1},minItems:1,description:"Drift keys whose files should not be asked about yet."}},anyOf:[{required:["key"]},{required:["keys"]}],additionalProperties:!1},run:async o=>{let s=Array.isArray(o.keys)?o.keys.filter(i=>typeof i=="string"):xn(o.key)?[xn(o.key)]:[];return!s.length||s.some(i=>!i.trim())?{isError:!0,text:"Snooze requires existing drift keys from grum_check. If the change has not been implemented and there is no drift, leave it unchanged; no snooze is needed. Snooze does not schedule a reminder or notification."}:e.snooze(s)}},{name:"grum_review_status",description:n.grum_review_status,inputSchema:{type:"object",properties:{key:{type:"string",description:"One drift key. Omit to list all."}},additionalProperties:!1},run:o=>e.status(xn(o.key)||void 0)},{name:"grum_setup",description:n.grum_setup,inputSchema:{type:"object",properties:{agent:{type:"string",enum:["cursor","codex","claude","generic"],description:"The agent the user is running."},project:{type:"string",description:"Project id from a choice."},create:{type:"boolean",description:"Create a workspace. Required when none exists."},name:{type:"string"},site:{type:"string",description:"Live site URL, or none."},localUrl:{type:"string",description:"Local app URL, a repo file, or none."},switchServer:{type:"boolean"},remove:{type:"boolean",description:"Remove Grum from this agent only."},dropPreCommit:{type:"boolean"}},required:["agent"],additionalProperties:!1},run:o=>e.setup(o)},{name:"grum_doctor",description:n.grum_doctor,inputSchema:{type:"object",properties:{agent:{type:"string",enum:["cursor","codex","claude","generic"]}},required:["agent"],additionalProperties:!1},run:o=>e.doctor(o)}]}var no=(e,t)=>({jsonrpc:"2.0",id:e,result:t}),Vs=(e,t,n)=>({jsonrpc:"2.0",id:e,error:{code:t,message:n}});async function Zg(e,t,n){if(!e||typeof e!="object")return Vs(null,-32600,"Invalid request");let r=e,o=!("id"in r),s=r.id??null;if(typeof r.method!="string")return o?null:Vs(s,-32600,"Invalid request");let i=r.params??{};switch(r.method){case"initialize":return no(s,{protocolVersion:typeof i.protocolVersion=="string"?i.protocolVersion:X$,capabilities:{tools:{listChanged:!1}},serverInfo:n});case"ping":return no(s,{});case"tools/list":return no(s,{tools:t.map(({name:l,description:a,inputSchema:c})=>({name:l,description:a,inputSchema:c}))});case"tools/call":{let l=t.find(c=>c.name===i.name);if(!l)return Vs(s,-32602,`Unknown tool: ${String(i.name)}`);let a=i.arguments&&typeof i.arguments=="object"?i.arguments:{};try{let c=await l.run(a);return no(s,{content:[{type:"text",text:c.text}],isError:c.isError??!1})}catch(c){return no(s,{content:[{type:"text",text:c instanceof Error?c.message:String(c)}],isError:!0})}}default:return o?null:Vs(s,-32601,`Method not found: ${r.method}`)}}var Ql="eleendlnhpjgefeeagfopijmojkgghbo",Yg=`https://chromewebstore.google.com/detail/grum/${Ql}`;function Qg(e){let{hostname:t}=new URL(e);return["localhost","127.0.0.1","[::1]"].includes(t)?"dev":"store"}var Y$="https://app.grum.so",Q$="npx --yes --prefer-online @grum-os/cli@latest setup";function pt(e){let{hostname:t,origin:n}=new URL(e);return["localhost","127.0.0.1","[::1]"].includes(t)?"dev":n===Y$?"prod":t}function em(e,t){return e||t||null}function Hs(e){let{repo:t,build:n,command:r}=e;if(!t||new URL(t).origin===new URL(n).origin)return{kind:"go"};let o=pt(n),s=`You're running Grum ${o} (${n}), but this repo is set up for ${pt(t)} (${t}).`;return e.interactive?{kind:"ask",question:`${s} Switch this repo to ${o}?`,warning:o==="prod"?"This restores the published commands in the .cursor/ files your repo tracks.":`This changes .cursor/ files your repo tracks. Don't commit them while on ${o}; switch back with \`${Q$}\`.`}:{kind:"refuse",message:`${s} Nothing was changed. To switch this repo, run this in a terminal: ${r} setup`}}function ec(e){let t=[{ok:!0,label:`Grum ${pt(e.server)} (${e.server})`},{ok:!0,label:`Signed in, linked to ${e.project}`}];return e.extension&&t.push(e.extension.connected?{ok:!0,label:"Chrome extension connected"}:{ok:!1,label:"Chrome extension not connected (needed to capture pages behind a sign-in)",fix:e.extension.build==="store"?`Add Grum from the Chrome Web Store (${Yg}), click its icon in Chrome's toolbar, then Connect.`:'This is a local Grum: in grum-app-v3 run `npm run extension:build`, load `extension/chrome/dist` unpacked in chrome://extensions (Developer mode), click "Grum Dev" in the toolbar, then Connect.'}),e.chromeProfile&&t.push({ok:!0,label:`Captures open in the Chrome profile "${e.chromeProfile}", which has the extension`}),e.tools==="on"?t.push({ok:!0,label:"Grum's tools are on in Cursor"}):e.tools==="off"&&t.push({ok:!1,label:"Grum's tools are off in Cursor, so its agent can't ask for review",fix:`In Cursor, ${Ye}`}),e.localApp?e.localApp.status===null?t.push({ok:!1,label:`Local app not answering at ${e.localApp.url}`,fix:"Start your dev server (for example `npm run dev`) before asking for review."}):e.localApp.status>=500?t.push({ok:!1,label:`Local app answers with an error (${e.localApp.status}) at ${e.localApp.url}`,fix:"Check your dev server's output: a review would capture the error page."}):t.push({ok:!0,label:`Local app answering at ${e.localApp.url}`}):t.push({ok:!1,label:"No local app URL saved, so a review can't show how a change renders",fix:"Run setup in a terminal and give your dev server's URL (like http://localhost:3000)."}),t}var eC="\x1B[38;2;196;160;0m",tC="\x1B[0m";function tc(e){return`${eC}${e}${tC}`}function nc(e){return e.map(t=>{let n=`${t.ok?"\u2713":"\u2717"} ${t.label}`;return`${t.ok?n:tc(n)}${t.fix?`
|
|
139
139
|
${t.fix}`:""}`}).join(`
|
|
140
140
|
`)}function tm(e,t,n,r){let o=e?.profile,s=o?.info_cache;if(!s||typeof s!="object")return null;let i=new Set([...n,...r==="store"?[Ql]:[]]),l=Object.keys(s).sort((a,c)=>a===o?.last_used?-1:c===o?.last_used?1:0);for(let a of l)if((t[a]??[]).some(u=>{let p=u?.extensions?.settings;return!p||typeof p!="object"?!1:Object.entries(p).some(([d,f])=>f?.state!==0&&(i.has(d)||r==="dev"&&typeof f?.path=="string"&&/[\\/]extension[\\/]chrome[\\/]dist[\\/]?$/.test(f.path)))})){let u=s[a]?.name;return{dir:a,name:typeof u=="string"&&u?u:a}}return null}import{spawn as nC}from"node:child_process";import{existsSync as rC,readFileSync as oC}from"node:fs";import{homedir as nm}from"node:os";import xe from"node:path";function sC(e,t){if(t){if(!/^(Default|Profile \d+)$/.test(t)||!rC(xe.join(e,t)))throw new Error("The selected Chrome profile does not exist.");return t}}async function rm(e,t){let n=xe.basename(e)==="open";await new Promise((r,o)=>{let s=nC(e,t,{detached:!0,stdio:["ignore","ignore",n?"pipe":"ignore"]}),i="";s.stderr?.on("data",l=>{i.length<500&&(i+=l.toString("utf8"))}),s.once("error",o),n?s.once("close",l=>l===0?r():o(new Error(`${xe.basename(e)} exited with code ${l}${i.trim()?`: ${i.trim()}`:""}`))):s.once("spawn",()=>{s.unref(),r()})})}async function om(e,t={}){if((t.platform??process.platform)!=="darwin")throw new Error("Safari capture is supported on macOS only.");await(t.launch??rm)("/usr/bin/open",["-a","Safari",e])}async function sm(e,t={}){let n=t.env??process.env;if(n.GRUM_CAPTURE_OPEN==="manual")throw new Error("Automatic Chrome opening is disabled.");let r=t.platform??process.platform,o=t.home??nm(),s=t.profileRoot??(r==="darwin"?xe.join(o,"Library","Application Support","Google","Chrome"):r==="win32"?xe.join(n.LOCALAPPDATA||xe.join(o,"AppData","Local"),"Google","Chrome","User Data"):xe.join(n.XDG_CONFIG_HOME||xe.join(o,".config"),"google-chrome")),i=sC(s,t.profile),l=i?[`--profile-directory=${i}`,"--ignore-profile-directory-if-not-exists"]:[],a=r==="darwin"?[{command:"/usr/bin/open",args:["-a","Google Chrome",e,...l.length?["--args",...l]:[]]}]:r==="win32"?[n.LOCALAPPDATA,n.PROGRAMFILES,n["PROGRAMFILES(X86)"]].filter(c=>!!c).map(c=>({command:xe.join(c,"Google","Chrome","Application","chrome.exe"),args:[...l,e]})):["google-chrome","google-chrome-stable"].map(c=>({command:c,args:[...l,e]}));for(let c of a)try{await(t.launch??rm)(c.command,c.args);return}catch{}throw new Error("Couldn't open your existing Chrome profile.")}function iC(e=process.platform,t=nm(),n=process.env){return e==="darwin"?xe.join(t,"Library","Application Support","Google","Chrome"):e==="win32"?xe.join(n.LOCALAPPDATA||xe.join(t,"AppData","Local"),"Google","Chrome","User Data"):xe.join(n.XDG_CONFIG_HOME||xe.join(t,".config"),"google-chrome")}function im(e,t,n=iC()){let r=l=>{try{return JSON.parse(oC(l,"utf8"))}catch{return null}},o=r(xe.join(n,"Local State")),s=o?.profile?.info_cache;if(!s)return null;let i={};for(let l of Object.keys(s))i[l]=["Preferences","Secure Preferences"].map(a=>r(xe.join(n,l,a)));return tm(o,i,e,t)}import bm from"node:path";import{mkdirSync as fC,readFileSync as gC,rmSync as ic,writeFileSync as dm}from"node:fs";import Re from"node:path";import{fileURLToPath as dC}from"node:url";import{chmodSync as aC,mkdirSync as lC,readdirSync as cC,readFileSync as am,rmSync as uC,writeFileSync as pC}from"node:fs";import{homedir as or}from"node:os";import ro from"node:path";function lm(e,t=process.env){let n=new URL(t.GRUM_API_ORIGIN?.trim()||e);if(!/^https?:$/.test(n.protocol)||n.username||n.password)throw new Error("GRUM_API_ORIGIN must be an http or https origin.");return n.origin}var qs=(e=or())=>ro.join(e,".config","grum");function rc(e,t=or()){return ro.join(qs(t),"credentials",`${e.replace(/[^a-z0-9]+/gi,"_")}.json`)}function Js(e,t=process.env,n=or()){let r=t.GRUM_TOKEN?.trim();if(r)return{token:r,apiOrigin:e};try{let o=JSON.parse(am(rc(e,n),"utf8"));return typeof o.token=="string"&&o.token&&o.apiOrigin===e?{token:o.token,apiOrigin:e}:null}catch{return null}}function cm(e,t=or()){let n=rc(e.apiOrigin,t);lC(ro.dirname(n),{recursive:!0,mode:448}),pC(n,`${JSON.stringify(e,null,2)}
|
|
141
|
-
`,{mode:384}),aC(n,384)}function um(e,t=or()){uC(rc(e,t),{force:!0})}function pm(e=or()){let t=ro.join(qs(e),"credentials"),n;try{n=cC(t).filter(r=>r.endsWith(".json"))}catch{return[]}return n.flatMap(r=>{try{let o=JSON.parse(am(ro.join(t,r),"utf8"));return typeof o.token=="string"&&o.token&&typeof o.apiOrigin=="string"?[{token:o.token,apiOrigin:o.apiOrigin}]:[]}catch{return[]}})}var V=lm("https://app.grum.so"),sr=globalThis.__GRUM_VERSION__??"0.0.0-dev",Ks="@grum-os/cli",oc=`node ${dC(import.meta.url)}`,sc=Ks?{hook:`npx --
|
|
141
|
+
`,{mode:384}),aC(n,384)}function um(e,t=or()){uC(rc(e,t),{force:!0})}function pm(e=or()){let t=ro.join(qs(e),"credentials"),n;try{n=cC(t).filter(r=>r.endsWith(".json"))}catch{return[]}return n.flatMap(r=>{try{let o=JSON.parse(am(ro.join(t,r),"utf8"));return typeof o.token=="string"&&o.token&&typeof o.apiOrigin=="string"?[{token:o.token,apiOrigin:o.apiOrigin}]:[]}catch{return[]}})}var V=lm("https://app.grum.so"),sr=globalThis.__GRUM_VERSION__??"0.0.0-dev",Ks="@grum-os/cli",oc=`node ${dC(import.meta.url)}`,sc=Ks?{hook:`npx --yes --prefer-offline ${Ks}`,mcp:`npx --yes --prefer-online ${Ks}@latest`,preCommit:`npx --yes --prefer-online ${Ks}@latest`}:{hook:oc,mcp:oc,preCommit:oc},Q=sc.mcp;function re(e){try{return gC(e,"utf8")}catch{return null}}function $t(e){let t=re(e);if(t===null)return null;try{return JSON.parse(t)}catch{return null}}function ve(e,t){fC(Re.dirname(e),{recursive:!0}),dm(e,t)}function vn(e,t,n){ve(Re.join(e.stateDir,t),`${JSON.stringify(n,null,2)}
|
|
142
142
|
`);let r=Re.join(e.repoDir,".gitignore"),o=Ns(re(r));o.changed&&dm(r,o.text)}var Ge=e=>ks($t(Re.join(e.stateDir,"contract.json"))),Sn=(e,t)=>vn(e,"contract.json",t),Kt=e=>Dg($t(Re.join(e.stateDir,"drift.json")),new Date().toISOString(),V),$n=(e,t)=>vn(e,"drift.json",{...t,apiOrigin:V});function ir(e){let t=n=>{let r=$t(Re.join(e.stateDir,n));return typeof r?.apiOrigin=="string"?r.apiOrigin:null};return em(t("contract.json"),t("drift.json"))}function Xs(e){let t=["contract.json","drift.json","project.json"].flatMap(n=>{let r=Re.join(e.stateDir,n),o=re(r);return o===null?[]:[{file:r,text:o}]});for(let{file:n}of t)ic(n,{force:!0});return()=>{for(let{file:n,text:r}of t)ve(n,r)}}var Zs=(e,t)=>Re.join(e.stateDir,"edits",t.replace(/[^A-Za-z0-9_-]/g,"_")||"_"),fm=(e,t)=>ic(Zs(e,t),{recursive:!0,force:!0});function oo(e){let t=$t(Re.join(e.stateDir,"project.json"));return typeof t?.id=="string"?t.id:null}var ar=(e,t)=>vn(e,"project.json",{id:t});function Ys(e){return Gg($t(Re.join(e.stateDir,"config.json")))}function Ct(e){let t=$t(Re.join(e.stateDir,"local-app.json"));return Fr(t?.url,e.repoDir)}var lr=(e,t)=>vn(e,"local-app.json",{url:t}),gm=e=>ic(Re.join(e.stateDir,"local-app.json"),{force:!0});function mm(e){return $t(Re.join(e.stateDir,"local-app.json"))?.skipped===!0&&!Ct(e)}var hm=e=>vn(e,"local-app.json",{skipped:!0});function ym(e,t){return $t(Re.join(e.stateDir,"site.json"))?.skipped===t}var ac=(e,t)=>vn(e,"site.json",{skipped:t});function Qs(e){let t=i=>$t(Re.join(e.stateDir,i))??{},n=t("capture.json"),r=t("config.json"),o=[n.browser,r.captureBrowser].find(i=>i==="chrome"||i==="safari"),s=[n.chromeProfile,r.chromeProfile].find(i=>typeof i=="string");return{...o?{browser:o}:{},...s?{chromeProfile:s}:{}}}var ei=(e,t)=>vn(e,"capture.json",t);function lc(e,t){try{let n=Kg(r=>re(bm.join(e,r)),t);for(let r of n)ve(bm.join(e,r.file),r.text);return n.map(r=>r.file)}catch{return[]}}function mC(e,t="/"){return typeof e=="string"&&e.startsWith("/")&&!e.startsWith("//")&&!e.startsWith("/\\")?e:t}function so(e,t){let n=new URLSearchParams({reconnect:"1"}),r=mC(e,"");return r&&n.set("next",r),t&&n.set("repo",t),`/sign-in?${n}`}var At=["Reading source files","Extracting tokens","Detecting drift","Analyzing source","Scanning website","Building design contract"],hC=["files","bytes","pages","pictures","fonts","spots"],ti={"source-commit":{step:0,text:"checking the latest commit"},"source-list":{step:0,text:"listing files"},"source-download":{step:0,text:"downloading"},"source-unpack":{step:0,text:"unpacking"},"source-read":{step:0,text:""},"source-shared":{step:0,text:"reading shared folders"},"source-wait":{step:0,text:"waiting for GitHub to allow more requests"},guidelines:{step:3,text:"reading guidelines"},buttons:{step:3,text:"checking buttons"},inputs:{step:3,text:"checking inputs"},"site-pages":{step:4,text:"finding pages"},"site-browser":{step:4,text:"starting the browser"},"page-load":{step:4,text:"loading"},"page-settle":{step:4,text:"settling the page"},"page-fonts":{step:4,text:"waiting for fonts"},"page-read":{step:4,text:"reading colors and fonts"},"page-pictures":{step:4,text:"taking example pictures"},"page-buttons":{step:4,text:"finding buttons"},"page-button-pictures":{step:4,text:"taking button pictures"},"page-inputs":{step:4,text:"finding inputs"},"page-input-pictures":{step:4,text:"taking input pictures"},"page-spots":{step:4,text:"checking flagged spots"},"final-compare":{step:5,text:"comparing with the last scan"},"final-review":{step:5,text:"queuing review items"},"final-fonts":{step:5,text:"saving fonts"},"final-pictures":{step:5,text:"saving pictures"},"final-save":{step:5,text:"saving results"}},tO=Object.keys(ti);function xm(e){return typeof e=="string"&&Object.hasOwn(ti,e)}function yC(e){if(e<1024)return`${e} B`;if(e<1024*1024){let n=e/1024;return n>=10?`${Math.round(n)} KB`:`${n.toFixed(1)} KB`}let t=e/(1024*1024);return t>=10?`${Math.round(t)} MB`:`${t.toFixed(1)} MB`}function It(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0?e:null}function Cn(e){let t=It(e);return t!==null&&t>0?t:null}function vm(e){let t=typeof e=="number"&&Number.isFinite(e)?e:0;return Math.min(Math.max(Math.trunc(t),0),At.length-1)}function bC(e){let t=Cn(e?.page),n=Cn(e?.pages);return t&&n?{page:Math.min(t,n),pages:n}:null}var wC=1024*1024;function cc(e){let t=Math.round(e/wC*10)/10;return Number.isInteger(t)?String(t):t.toFixed(1)}function wm(e,t){let n=It(t.done),r=Cn(t.total);if(e==="source-download")return n===null?"":t.unit==="bytes"&&r&&n<=r?`${Math.min(100,Math.floor(n/r*100))}% (${cc(n)} of ${cc(r)} MB)`:n>0?`${cc(n)} MB`:"";if(n===null)return"";let o=e==="source-read"||e==="source-shared"?" files":"";return r&&n<=r?`${n} of ${r}${o}`:o&&n>0?`${n}${o}`:String(n)}function kC(e){let t=It(e.files),n=It(e.bytes);if(t===null&&n===null)return"";let r=[];return t!==null&&r.push(`${t} ${t===1?"file":"files"}`),n!==null&&r.push(yC(n)),r.join(" \xB7 ")}function km(e,t){let n=vm(e),r=At[n],o=xm(t?.operation)?t.operation:null,s=o&&ti[o].step===n?o:null,i=r==="Scanning website"?bC(t):null,l=i?`Scanning page ${i.page} of ${i.pages}`:r;if(!s)return l;if(s==="source-download"){let d=wm(s,t);return d?`Downloading repository: ${d}`:"Downloading repository"}let a=ti[s].text,c=wm(s,t),u=s==="buttons"||s==="inputs"?kC(t):"",p=[[a,c].filter(Boolean).join(" "),u].filter(Boolean).join(" \xB7 ");return p?`${l}: ${p}`:l}function uc(e,t){let n=It(e),r=Cn(t);return n===null||r===null?null:Math.min(n,r)/r}function xC(e,t){let n=At[e];if(n==="Scanning website"){let r=Cn(t?.pages);if(!r)return uc(t?.done,t?.total);let o=Math.min(It(t?.pagesDone)??Math.max((It(t?.page)??1)-1,0),r),s=uc(t?.done,t?.total),i=o<r&&s!==null?s:0;return(o+i)/r}if(n==="Reading source files"&&t?.operation==="source-shared"){let r=Cn(t.files),o=Cn(t.total);if(r!==null&&o!==null&&t.unit==="files")return Math.min(r+(It(t.done)??0),r+o)/(r+o);if(r!==null)return null}return uc(t?.done,t?.total)}function vC(e,t){let n=Math.min(Math.max(Number.isFinite(e)?Math.trunc(e):0,0),At.length);if(n>=At.length||t?.status==="done")return{percent:100,measured:!0};let r=xC(n,t),o=Math.round((n+(r??0))/At.length*100);if(r!==null)return{percent:Math.min(99,o),measured:!0};let s=Math.min(It(t?.percent)??0,99);return{percent:Math.min(99,Math.max(o,s)),measured:!1}}function SC(e,t){return vC(e,t).percent}var pc=20;var $C="The scan stopped without finishing.";function dc(e){return e.liveness==="stopped"?$C:e.status!=="running"?null:e.liveness==="quiet"?e.heartbeatAgeSeconds===null?"No update from the scan yet.":`No update from the scan for ${e.heartbeatAgeSeconds}s.`:e.liveness==="working"&&e.heartbeatAgeSeconds!==null&&e.progressAgeSeconds!==null&&e.progressAgeSeconds>=pc?"Still working. This part can take a while.":null}var CC=["none","running","done","failed"],RC=["working","quiet","stopped","done","failed","none"];function qe(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0?Math.trunc(e):null}function Rt(e){return typeof e=="string"?e:null}function Rn(e){if(!e||typeof e!="object")return null;let t=e,n=CC.find(u=>u===t.status);if(!n)return null;let r=vm(qe(t.step)),o=RC.find(u=>u===t.liveness)??(n==="running"?"working":n),s=xm(t.operation)?t.operation:null,i=t.diagnosticCode==="SCAN_NO_HEARTBEAT"||t.diagnosticCode==="SCAN_FAILED"?t.diagnosticCode:o==="stopped"?"SCAN_NO_HEARTBEAT":n==="failed"?"SCAN_FAILED":null,l=hC.find(u=>u===t.unit)??null,a=qe(t.percent),c=Rt(t.label);return{scanId:Rt(t.scanId),status:n,step:r,operation:s,page:qe(t.page),pages:qe(t.pages),pagesDone:qe(t.pagesDone),done:qe(t.done),total:qe(t.total),unit:l,files:qe(t.files),bytes:qe(t.bytes),label:c??km(r),notice:Rt(t.notice),percent:a===null?SC(r):Math.min(a,100),error:Rt(t.error),diagnosticCode:i,lastKnown:Rt(t.lastKnown)??(n==="failed"?km(r,{operation:s}):null),liveness:o,observedAt:Rt(t.observedAt)??new Date(0).toISOString(),startedAt:Rt(t.startedAt),heartbeatAt:Rt(t.heartbeatAt),progressAt:Rt(t.progressAt),elapsedSeconds:qe(t.elapsedSeconds),heartbeatAgeSeconds:qe(t.heartbeatAgeSeconds),progressAgeSeconds:qe(t.progressAgeSeconds)}}var ni=20*6e4,Sm=5*6e4,$m=1500,AC=6e4,fc=15e3,Cm=500,Rm="Ctrl+C stops waiting. The scan keeps going on Grum's servers.";function gc(e){let t=Math.max(0,Math.floor(e));if(t<60)return`${t}s`;let n=Math.floor(t/60);return n<60?`${n}m ${String(t%60).padStart(2,"0")}s`:`${Math.floor(n/60)}h ${String(n%60).padStart(2,"0")}m`}function Am(e){let{view:t,receivedAtMs:n,waitStartedMs:r,nowMs:o}=e;return t?.elapsedSeconds!=null&&n!=null?t.elapsedSeconds+Math.max(0,Math.floor((o-n)/1e3)):Math.max(0,Math.floor((o-r)/1e3))}function Im(e){let{repo:t,view:n,elapsedSeconds:r}=e,o=[`Reading ${t}`];n&&n.status==="running"&&o.push(`${n.percent}%`),n&&o.push(n.label),o.push(gc(r));let s=n?dc(n):null;return s&&o.push(s),o.join(" \xB7 ")}function Em(e,t){return`Reading ${e} \xB7 can't reach Grum for progress, still trying \xB7 ${gc(t)}`}var Tm={key:null,label:null,at:null};function _m(e,t,n){let r=[t.status,t.step,t.page??"",t.liveness].join("|"),o=e.key!==r,s=e.label!==t.label,i=e.at===null||n-e.at>=fc;return o||s&&i?{print:!0,state:{key:r,label:t.label,at:n}}:{print:!1,state:e}}function Om(e,t,n){return e===null||t-e>=n}function jm(e){return Math.max(0,Math.floor(e)).toLocaleString("en-US")}function Mm(e){return`Reading files: ${jm(e)}`}function Pm(e){return`Checking ${jm(e)} ${e===1?"file":"files"} against the contract`}function Dm(e){if(e.status===200)return{kind:"ready"};let t=typeof e.body.error=="string"?e.body.error:`Grum answered ${e.status}.`;if(e.status===0||e.status>=500)return{kind:"unreachable",detail:t};let n=Rn(e.body.scan);return e.body.code!=="not-scanned"?{kind:"failed",detail:t,view:n}:n?.status==="failed"?{kind:"failed",detail:n.error??t,view:n}:{kind:"progress",view:n}}function Lm(e,t,n=ni){return t-e>=n}function Nm(e,t){return e!==null&&t-e>=AC}function Fm(e){let{repo:t,view:n,checkCommand:r,buildingUrl:o}=e,s=n?` Last seen: ${n.label} (${n.percent}%).`:"",i=n?.liveness==="quiet"&&n.heartbeatAgeSeconds!=null?` Grum last heard from the scan ${n.heartbeatAgeSeconds}s ago.`:"";return`Stopped waiting for the scan of ${t} after ${gc((e.limitMs??ni)/1e3)}. It keeps going on Grum's servers.${s}${i} Run \`${r}\` to check on it, or watch it at ${o}.`}function Um(e){return`Stopped waiting for the scan of ${e.repo}. It keeps going on Grum's servers. Run \`${e.checkCommand}\` to check on it, or watch it at ${e.buildingUrl}.`}function Gm(e){return`Couldn't read progress for ${e.repo}. ${e.detail} The scan may still be running. Run \`${e.checkCommand}\` to check, or watch it at ${e.buildingUrl}.`}var IC=10,EC=30;function mc(e){return{scanId:e.scanId,percent:e.percent,step:Math.min(e.step+1,At.length),steps:At.length,label:e.label,elapsedSeconds:e.elapsedSeconds,heartbeatAgeSeconds:e.heartbeatAgeSeconds,progressAgeSeconds:e.progressAgeSeconds,liveness:e.liveness,retryAfterSeconds:e.liveness==="quiet"?EC:IC}}function zm(e){let t="Do not block this chat, do not start another scan, and do not hunt GitHub settings.";if(!e||e.status!=="running")return`The scan is on Grum's servers. A large repo or a slow GitHub connection can take a few minutes. Run the same command again shortly. Tell the person if it is still running after several tries. ${t}`;let n=mc(e),r=`step ${n.step} of ${n.steps}, about ${n.percent}%: ${n.label}`,o=`Run the same command again in about ${n.retryAfterSeconds} seconds`,s=dc(e);return e.liveness==="quiet"?`Grum has not heard from the scan for ${e.heartbeatAgeSeconds??"several"} seconds. Last known: ${r}. It may be slow or may have stopped. Tell the person that in one line. ${o}. Tell them if it is still silent after a few tries. ${t}`:e.progressAgeSeconds!==null&&e.progressAgeSeconds>=pc?`The scan is still reporting in, but this part has not advanced for ${e.progressAgeSeconds} seconds. Now: ${r}. Some steps take a while. Tell the person in one line. ${o}. ${s?`${s} `:""}${t}`:`The scan is working: ${r}. Tell the person in one line what it is doing. ${o}. ${t}`}import hc from"node:path";function Je(e){return e.replace(/\\/g,"/").replace(/^\.\/?/,"").replace(/^\/+|\/+$/g,"")}function Xt(e,t){let n=Je(t),r=e.replace(/\\/g,"/");return n?r.startsWith(`${n}/`)?r.slice(n.length+1):null:r}function Vm(e,t){let n=Je(e).split("/").filter(Boolean),r=t.replace(/\\/g,"/").split("/").filter(Boolean),o=0;for(;o<n.length&&o<r.length&&n[o]===r[o];)o++;return[...Array(n.length-o).fill(".."),...r.slice(o)].join("/")}var Hm=/\.(css|scss|sass|less|tsx|jsx|vue|svelte|astro)$|(^|\/)tailwind\.config\.|\.tokens\.json$/i,TC=200;function qm(e){return Dr.test(`${e}/`)}function bc(e){let t="",n=0,r=!1,o=!1;for(;n<e.length;){let s=e[n];if(r){t+=s,o?o=!1:s==="\\"?o=!0:s==='"'&&(r=!1),n++;continue}if(s==='"'){r=!0,t+=s,n++;continue}if(s==="/"&&e[n+1]==="/"){let i=e.indexOf(`
|
|
143
|
-
`,n);n=i<0?e.length:i;continue}if(s==="/"&&e[n+1]==="*"){let i=e.indexOf("*/",n+2);n=i<0?e.length:i+2;continue}t+=s,n++}try{return JSON.parse(t.replace(/,(\s*[}\]])/g,"$1"))}catch{return null}}function _C(e){return e==="package.json"?"":e.slice(0,-13)}function OC(e){let t=bc(e);if(!t||typeof t!="object")return[];let n=t,r=[];for(let o of["dependencies","devDependencies","peerDependencies"]){let s=n[o];s&&typeof s=="object"&&r.push(...Object.keys(s))}return r}function jC(e){let t=new Map,n=[...e.keys()].filter(o=>!qm(o)).sort(),r=0;for(let o of n){if(r>=TC)break;let s=e.get(o);if(!s)continue;r++;let i=bc(s),l=i&&typeof i=="object"?i.name:null;typeof l!="string"||!l||t.has(l)||t.set(l,_C(o))}return t}function yc(e,t){let n=`${e}/`;return t.some(r=>!qm(r.path)&&r.path.startsWith(n)&&Hm.test(r.path))}function oi(e){let t=e.lastIndexOf("/");return t<0?"":e.slice(0,t)}function cr(e,t){let n=[...oi(e)?oi(e).split("/"):[],...t.split("/")],r=[];for(let o of n)if(!(!o||o===".")){if(o===".."){if(!r.length)return null;r.pop();continue}r.push(o)}return r.join("/")}function Bm(e,t){let n=Je(t);return n?e!==n&&!e.startsWith(`${n}/`):!1}function Wm(e){if(!e||e.startsWith(".")||e.startsWith("/"))return null;let t=e.split("/");return e.startsWith("@")?t.length>=2?`${t[0]}/${t[1]}`:null:t[0]||null}function MC(e,t){let n=Hm.test(e)?oi(e):e;for(;n;){if(t.has(n))return n;let r=n.lastIndexOf("/");if(r<0)break;n=n.slice(0,r)}return t.has("")?"":null}function ri(e,t,n,r){if(!e||!Bm(e,t))return null;let o=MC(e,n);return!o||!Bm(o,t)?null:yc(o,r)?o:null}function PC(e){return[...e.matchAll(/["']([^"']+)["']/g)].map(t=>t[1]).filter(Boolean)}function DC(e){return oi(e)}function Jm(e,t,n,r){let o=Je(e);if(!o)return[];let s=jC(n),i=new Set([...s.values()].filter(Boolean)),l=new Map,a=(f,m)=>{!f||l.has(f)||l.set(f,m)};if(n.get(`${o}/package.json`)){let f=new Set([o]),m=[{folder:o,via:null}];for(;m.length;){let g=m.shift(),S=n.get(g.folder?`${g.folder}/package.json`:"package.json");if(S)for(let h of OC(S)){let b=s.get(h);!b||f.has(b)||(f.add(b),yc(b,t)&&a(b,g.via?`${o} depends on ${h} through ${g.via}`:`${o} depends on ${h}`),m.push({folder:b,via:h}))}}}let u=[...r.keys()].filter(f=>(f==="tsconfig.json"||f.endsWith("/tsconfig.json")||/(^|\/)tsconfig\.[^/]+\.json$/.test(f))&&(f===o||f.startsWith(`${o}/`)||!f.includes("/"))),p=new Set,d=u.filter(f=>f===`${o}/tsconfig.json`||f.startsWith(`${o}/tsconfig`));for(d.length||d.push(...u.filter(f=>f.startsWith(`${o}/`)));d.length;){let f=d.shift();if(p.has(f))continue;p.add(f);let m=r.get(f);if(!m)continue;let g=bc(m);if(!g||typeof g!="object")continue;let S=g,h=S.extends,b=Array.isArray(h)?h:[h];for(let x of b){if(typeof x!="string")continue;let v=x.startsWith(".")?cr(f,x):(()=>{let $=Wm(x),A=$?s.get($):void 0;if(!A)return null;let E=x.slice($.length).replace(/^\//,"");return E?`${A}/${E}`:A})();v&&r.has(v)&&d.push(v)}let k=typeof S.compilerOptions?.baseUrl=="string"?cr(f,S.compilerOptions.baseUrl):DC(f),y=S.compilerOptions?.paths;if(y&&typeof y=="object"&&k!=null){for(let x of Object.values(y))if(Array.isArray(x))for(let v of x){if(typeof v!="string")continue;let $=cr(`${k}/x`,v.replace(/\*.*$/,"").replace(/\/$/,"")||".");$&&a(ri($,o,i,t),`${o} references ${$} from tsconfig`)}}if(Array.isArray(S.references))for(let x of S.references){let v=x&&typeof x=="object"&&typeof x.path=="string"?x.path:null;if(!v)continue;let $=cr(f,v);$&&a(ri($,o,i,t),`${o} references ${$} from tsconfig`)}}for(let[f,m]of r){if(!f.startsWith(`${o}/`))continue;let g=/\.(css|scss|less)$/i.test(f),S=/(^|\/)tailwind\.config\./.test(f),h=[];if(g)for(let b of m.matchAll(/@(?:import|use|forward|source|config|plugin)\s+(?:url\(\s*)?["']([^"']+)["']/g))h.push(b[1]);if(S)for(let b of["content","presets"]){let k=new RegExp(`${b}\\s*:\\s*\\[([\\s\\S]*?)\\]`).exec(m);k?.[1]&&h.push(...PC(k[1]))}for(let b of h){if(b.startsWith(".")){let x=cr(f,b),v=x?ri(x,o,i,t):null;a(v,`${o} imports ${b}`);continue}let k=Wm(b),y=k?s.get(k):void 0;y&&yc(y,t)&&a(y,`${o} imports ${b}`)}if(/\.(tsx?|jsx?|mjs|cjs|vue|svelte|astro)$/i.test(f))for(let b of m.matchAll(/(?:from\s+|import\s*\(\s*|require\(\s*)["'](\.[^"']+)["']/g)){let k=b[1],y=cr(f,k),x=y?ri(y,o,i,t):null;a(x,`${o} imports ${k}`)}}return[...l.entries()].map(([f,m])=>({folder:f,reason:m})).sort((f,m)=>f.folder<m.folder?-1:1)}function Km(e){if(e.flag!==void 0)return Je(e.flag);let t=Je(e.config??"");return t||Je(e.projectRoot??"")}function si(e){if(e.flag!==void 0)return Je(e.flag);let t=hc.relative(e.repoDir,e.cwd);return!t||t==="."||t.startsWith("..")||hc.isAbsolute(t)?"":Je(t.split(hc.sep).join("/"))}var Xm=4e7,wc=5e3,ii=15e3;function io(e,t){let n=Math.max(1,Math.round(e/1e3));return t||e>=ii?{kind:"limit",seconds:n}:e>=wc?{kind:"slow",seconds:n}:{kind:"ok"}}function ao(e){return e.kind==="slow"?`Reading source took ${e.seconds}s. Grum skips videos and large images by name; a huge source tree or a slow disk can still take a while.`:e.kind==="limit"?"Reading source stopped at the 15s limit. Grum skips videos and large images by name, so this is a huge source tree, a slow disk, or a stalled git list. This used what it had so far.":null}var LC=/(^|\/)\.grum\//;function kc(e){return!Dr.test(`${e}/`)&&!LC.test(`${e}/`)}function xc(e,t){return cf(e,t)}function Zm(e){let t=Math.min(e.length,8e3);for(let n=0;n<t;n++)if(e[n]===0)return!0;return!1}var NC=/\.(css|scss|sass|less|tsx?|jsx?|mjs|cjs|vue|svelte|astro|html?)$/i;function Ym(e){return NC.test(e)}function Qm(e,t,n){let r=new Map(e);for(let o of t){let s=Xt(o.path,n);s===null||!kc(s.split("/").slice(0,-1).join("/")||".")||(o.content===null?r.delete(s):xc(s,o.content.length)&&r.set(s,o.content))}return r}function eh(e){let t=e.split("\0").filter(Boolean),n=[];for(let r=0;r<t.length;r++){let o=t[r];if(/^[RC]/.test(o)){let i=t[++r],l=t[++r];o.startsWith("R")&&i&&n.push({path:i,deleted:!0}),l&&n.push({path:l,deleted:!1});continue}let s=t[++r];s&&n.push({path:s,deleted:o.startsWith("D")})}return n}function ai(e){let t=/^(?:git@|ssh:\/\/git@|https?:\/\/(?:[^@/]+@)?)github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i.exec(e.trim());return t?`${t[1]}/${t[2]}`:null}var th=1,FC=["cursor","codex","claude","generic"],ur={cursor:"Cursor",codex:"Codex",claude:"Claude Code"};var UC={ready:0,"action-needed":2,error:1},GC="https://nodejs.org/en/download",li=22,zC="Give the person the URL in this result and ask them to open it. That Grum page checks the repository and walks them through it: Reconnect GitHub, then, if still blocked, Grum's Manage Access page on GitHub, where they grant the organization that owns the repository (or click Request when an org owner must approve). It says when Grum can read the repository. Grum is an OAuth app named Grum, not a GitHub App: do not send the person anywhere else on GitHub to hunt for it. Then run the same command again.";function BC(e){return e.scan==="failed"&&qn(e.scanMessage??"")}var WC=["blocked","needs-sign-in","needs-github","needs-workspace-create","needs-project-choice","needs-starter","needs-host-action","scan-running","needs-site","needs-extension","needs-local-app","ready"];function nh(e){return UC[e.exit]}function vc(e){let t=e.serverOrigin??"",n=d=>({ok:!1,result:QC(t,d)}),r=(e.agent??[]).map(d=>d.trim().toLowerCase());if(r.length!==1||!VC(r[0]))return n("Pass --agent with one of cursor, codex, claude or generic. generic can set up and check, and reports that automatic enforcement is unavailable.");let o=r[0];if((e.project?.length??0)>1)return n("Pass --project once, with the project id from the choices.");let s=e.project?.[0]?.trim()||null;if(e.project&&!s)return n("Pass --project with the project id from the choices.");let i=!!e.create,l=!!e.remove;if(i&&s)return n("Pass either --create or --project, not both.");if(i&&l)return n("Pass either --create or --remove, not both.");if(l&&e.switchServer)return n("Removing Grum from this repo is separate from switching servers. Run one, then the other.");if((e.name?.length??0)>1||(e.site?.length??0)>1)return n("Pass --name and --site at most once.");if((e.localUrl?.length??0)>1||(e.root?.length??0)>1)return n("Pass --local-url and --root at most once.");let a;if(e.site===void 0)a=void 0;else if(/^(none|skip)$/i.test(e.site[0]?.trim()??""))a=null;else{let d=gn(e.site[0]??"");if(!d)return n("Pass --site as an https URL or none. The live site is optional.");a=d}let c,u=null;if(e.localUrl===void 0)c=void 0;else{let d=HC(e.localUrl[0]??"");if(!d.ok)return n("Pass --local-url as an http(s) URL, a file in the repo such as ./index.html, or none.");c=d.url,u=d.kind}let p=(e.name?.[0]??"").trim().slice(0,80)||null;return e.probe&&(i||l||s||e.switchServer)?n("doctor only reports readiness. Run setup --agent to create, choose, switch or remove."):{ok:!0,request:{agent:o,projectId:s,create:i,remove:l,name:p,site:a,siteProvided:e.site!==void 0,localUrl:c,localProvided:e.localUrl!==void 0,localKind:u,root:e.root?.[0]?.trim()||null,switchServer:!!e.switchServer,dropPreCommit:!!e.dropPreCommit,probe:!!e.probe}}}function Ae(e,t){let n=t.probe?null:qC(e,t);return{result:JC(e,t),mutation:n}}function rh(e,t){let n=ur[t.agent==="generic"?"cursor":t.agent],r=t.dropPreCommit?" The pre-commit check was removed.":" The pre-commit check was left as it was.";return be(e,t,{open:[],fault:!1,checks:[{id:"adapter",evidence:"observed",status:"ok",label:`Removed Grum from ${n}.${r} This computer's sign-in and the Grum workspace were kept.`}],actions:[]})}function VC(e){return FC.includes(e??"")}function HC(e){let t=e.trim();if(/^(none|skip)$/i.test(t))return{ok:!0,url:null,kind:"none"};if(/^https?:\/\//i.test(t))try{let n=new URL(t);return n.username||n.password?{ok:!1}:{ok:!0,url:n.href,kind:"url"}}catch{return{ok:!1}}return!t||t.includes("..")||t.startsWith("/")||t.includes("\\")||/^[a-z][a-z0-9+.-]*:/i.test(t)?{ok:!1}:{ok:!0,url:t,kind:"file"}}function Sc(e){return!e.selectedId||!e.projects?null:e.projects.find(t=>t.id===e.selectedId)??null}function qC(e,t){if(!e.runtimeOk)return null;if(!!(e.repoServer&&e.repoServer!==e.serverOrigin))return t.switchServer?{kind:"switch-server"}:null;if(t.remove&&t.agent!=="generic")return{kind:"remove",agent:t.agent,dropPreCommit:t.dropPreCommit};if(e.offline||!e.signedIn||e.origin!=="github"||e.github==="missing"||e.github==="no-access")return null;let r=e.projects;if(!r)return null;let o=Sc(e);if(t.projectId&&r.some(l=>l.id===t.projectId)&&o?.id!==t.projectId)return{kind:"select",projectId:t.projectId};let s=e.kit;if(s?.needsRepo&&t.projectId===s.project.id&&r.length===0&&s.project.role==="owner"&&e.repoName)return{kind:"link-repo",projectId:s.project.id,repo:e.repoName};if(t.create&&r.length===0)return{kind:"create",name:t.name,site:t.siteProvided?t.site??null:null};if(!o)return null;let i=s&&s.project.id===o.id&&s.state!=="pushed";return e.scan==="needed"&&o.role==="owner"&&i?s.state==="not-written"?{kind:"write-starter",projectId:o.id}:null:t.agent!=="generic"&&!e.adapterWritten&&!i?{kind:"install",agent:t.agent}:(e.scan==="needed"||BC(e))&&o.role==="owner"?{kind:"start-scan",projectId:o.id}:e.scan!=="done"?null:t.siteProvided&&o.role==="owner"&&(t.site??null)!==(o.site??null)?{kind:"set-site",projectId:o.id,site:t.site??null}:t.siteProvided&&t.site===null&&o.role==="owner"&&!o.site&&!e.siteSkipped?{kind:"skip-site",projectId:o.id}:t.localProvided&&(t.localUrl!==e.localSaved||t.localUrl===null&&!e.localSkipped)?{kind:"set-local",url:t.localUrl??null}:null}function JC(e,t){let n=[],r=[],o=[],s=!1,i=Sc(e),l=ur[t.agent==="generic"?"cursor":t.agent];if(o.push({id:"runtime",evidence:"observed",status:e.runtimeOk?"ok":"missing",label:e.runtimeOk?`Node.js ${e.runtimeVersion} can run Grum.`:`Node.js ${e.runtimeVersion||"is missing"}; Grum needs ${li} or newer.`}),!e.runtimeOk)return n.push("blocked"),r.push({kind:"install-runtime",explanation:`Ask the person to install Node.js ${li} or newer from the browser, then restart the editor and run this again. Do not ask them to type a command.`,input:{url:GC,command:Se(e,t)}}),be(e,t,{open:n,actions:r,checks:o,fault:!1});let a=!!(e.repoServer&&e.repoServer!==e.serverOrigin);if(o.push({id:"server",evidence:"observed",status:a?"blocked":"ok",label:a?`This repo is set up for ${pt(e.repoServer)} (${e.repoServer}), and this Grum is ${pt(e.serverOrigin)} (${e.serverOrigin}).`:`Grum ${pt(e.serverOrigin)} (${e.serverOrigin}).`}),a)return n.push("blocked"),r.push({kind:"switch-server",explanation:"Ask before switching. Nothing was changed. Switching sets the previous server's local Grum files aside and sets this repo up here.",input:{flag:"--switch-server",command:Se(e,{...t,probe:!1},["--switch-server"])}}),be(e,t,{open:n,actions:r,checks:o,fault:!1});if(t.remove)return t.agent==="generic"&&(s=!0,n.push("blocked"),r.push({kind:"fix-error",explanation:`A generic agent has no Grum files to remove. To sign out this computer, run ${e.cli} logout.`})),be(e,t,{open:n,actions:r,checks:o,fault:s});if(e.offline)return s=!0,n.push("blocked"),o.push({id:"server-reach",evidence:"observed",status:"blocked",label:e.offlineMessage??"Grum could not be reached. Nothing was marked ready."}),r.push({kind:"fix-error",explanation:"Grum could not be reached, so this run is not ready. The connection is down or too slow (the CLI waits 15 seconds). Try the same command again once it answers. An offline check can still use a contract already saved on this computer, and that result is not current.",input:{command:Se(e,t)}}),be(e,t,{open:n,actions:r,checks:o,fault:s});if(e.origin==="absent"||e.origin==="unsupported")return s=!0,n.push("blocked"),o.push({id:"repository",evidence:"observed",status:"blocked",label:e.origin==="absent"?"This folder has no GitHub origin remote.":"This folder's origin remote is not a GitHub repository Grum can connect."}),r.push({kind:"fix-error",explanation:e.origin==="absent"?"Grum did not invent a git remote. Add the repository's GitHub origin, then run this again.":"Grum only connects a github.com origin. It did not change the remote."}),be(e,t,{open:n,actions:r,checks:o,fault:s});if(o.push({id:"sign-in",evidence:"observed",status:e.signedIn?"ok":"missing",label:e.signedIn?"Signed in on this computer.":e.signInExpired?"The sign-in on this computer expired or was revoked.":"This computer is not signed in to Grum."}),!e.signedIn){n.push("needs-sign-in");let g=e.pendingLogin;return r.push({kind:"approve-sign-in",explanation:g?`Open this Grum page, sign in, and approve code ${g.code}. Then run the same command again.`:"Run setup so Grum can start a browser sign-in. Doctor does not start one. The next result has the code and the page.",input:{...g?{url:g.url,code:g.code,expiresAt:g.expiresAt}:{},command:Se(e,{...t,probe:!1})}}),be(e,t,{open:n,actions:r,checks:o,fault:s})}let c=e.github==="no-access"?e.repoName:null;if(e.github==="missing"||e.github==="no-access")return n.push("needs-github"),o.push({id:"github",evidence:"observed",status:"missing",label:e.github==="no-access"?`This GitHub login cannot read ${c??"that repository"}: it is not found or access is denied.`:e.githubMessage??"GitHub is not connected for this account."}),r.push({kind:"connect-github",explanation:zC,input:{url:`${e.serverOrigin}${so(void 0,c??void 0)}`,command:Se(e,t)}}),be(e,t,{open:n,actions:r,checks:o,fault:s});if(e.projects===null)return s=!0,n.push("blocked"),o.push({id:"project",evidence:"unknown",status:"unknown",label:"Grum did not return this repository's workspaces."}),r.push({kind:"fix-error",explanation:"Run the same command again. No workspace was created.",input:{command:Se(e,t)}}),be(e,t,{open:n,actions:r,checks:o,fault:s});e.staleProjectId&&!i&&o.push({id:"project",evidence:"observed",status:"blocked",label:`The saved project ${e.staleProjectId} is not a workspace this account can use.`});let u=e.kit?.needsRepo===!0&&t.projectId===e.kit.project.id;if(t.projectId&&!u&&!e.projects.some(g=>g.id===t.projectId)){let g=e.projects.map(S=>({id:S.id,label:S.name}));return g.length||(s=!0),n.push(g.length?"needs-project-choice":"blocked"),r.push({kind:g.length?"choose-project":"fix-error",explanation:g.length?"That project id is not one this account can use for this repository. Ask which of these to use.":"That project id is not a workspace this account can use, and this repository has none yet.",input:g.length?{flag:"--project",choices:g,command:Se(e,t)}:void 0}),be(e,t,{open:n,actions:r,checks:o,fault:s})}let p=u?e.kit:null;if(e.projects.length===0&&p){let g=p.project.name;return p.project.role!=="owner"?(s=!0,n.push("blocked"),o.push({id:"project",evidence:"observed",status:"blocked",label:`Only an owner of ${g} can connect a repository to it.`}),r.push({kind:"fix-error",explanation:`${e.repoName} is not connected to ${g}, and only an owner of that workspace can connect it. Ask an owner. Nothing was changed.`}),be(e,t,{open:n,actions:r,checks:o,fault:s})):(n.push("needs-workspace-create"),o.push({id:"project",evidence:"observed",status:"missing",label:`${e.repoName} is not connected to the ${g} workspace yet.`}),r.push({kind:"link-workspace",explanation:`Connect ${e.repoName} to the ${g} workspace, which started from ${p.kit.name}. The person's request to set up this project already authorized it: say that you are connecting it, then run setup. A readiness check never connects it.`,input:{command:Se(e,{...t,probe:!1})}}),be(e,t,{open:n,actions:r,checks:o,fault:s}))}if(e.projects.length===0)return n.push("needs-workspace-create"),o.push({id:"project",evidence:"observed",status:"missing",label:`No Grum workspace you can access is connected to ${e.repoName}.`}),r.push({kind:"create-workspace",explanation:t.create?`Creating a Grum workspace for ${e.repoName}.`:`Create a Grum workspace for ${e.repoName}. The person's explicit setup request already authorized this; state that you are creating it, then run setup with --create. A readiness check never creates one.`,input:{flag:"--create",command:Se(e,{...t,probe:!1,create:!0})}}),be(e,t,{open:n,actions:r,checks:o,fault:s});if(!i)return n.push("needs-project-choice"),o.push({id:"project",evidence:"observed",status:"missing",label:`Several workspaces use ${e.repoName}. None is selected.`}),r.push({kind:"choose-project",explanation:"Ask which workspace to use. Pass its id as --project. Do not choose the first.",input:{flag:"--project",choices:e.projects.map(g=>({id:g.id,label:g.name})),command:Se(e,t)}}),be(e,t,{open:n,actions:r,checks:o,fault:s});o.push({id:"project",evidence:"observed",status:"ok",label:`Linked to ${i.name} (${e.repoName}).`});let d=e.scan==="needed"&&e.kit?.project.id===i.id?e.kit:null;if(d&&d.state!=="pushed"){n.push("needs-starter");let g=ZC(e,t,d,i.role);return o.push({id:"starter",evidence:"observed",status:"missing",label:g.label}),r.push(g.action),be(e,t,{open:n,actions:r,checks:o,fault:s})}XC(e,t,l,n,r,o);let f=e.scan==="running"&&e.scanProgress?.status==="running"?e.scanProgress:null;if(e.scan==="failed"?(s=!0,n.push("blocked"),o.push({id:"scan",evidence:"observed",status:"blocked",label:e.scanMessage??"The first scan failed."}),r.push({kind:"fix-error",explanation:"The scan failed. Report that and stop. Do not start another scan unless the person asks."})):e.scan==="running"||e.scan==="needed"?(n.push("scan-running"),o.push({id:"scan",evidence:"observed",status:"missing",label:e.scan==="running"?f?`Grum is still reading ${e.repoName}: ${f.label} (${f.percent}%).`:`Grum is still reading ${e.repoName}.`:`Grum has not finished reading ${e.repoName}.`}),r.push({kind:"wait-for-scan",explanation:zm(f),input:{command:Se(e,t),...f?mc(f):{}}})):e.scan==="done"?o.push({id:"code-check",evidence:"observed",status:"ok",label:"Code checks can run against the current contract. That is separate from capture."}):o.push({id:"code-check",evidence:"unknown",status:"unknown",label:"No contract has been checked on this run."}),e.scan!=="done")return be(e,t,{open:n,actions:r,checks:o,fault:s});t.siteProvided&&i.role!=="owner"&&(t.site??null)!==(i.site??null)?o.push({id:"site",evidence:"observed",status:"blocked",label:"Only an owner can change the live site, so it was left as it is."}):i.site?o.push({id:"site",evidence:"observed",status:"ok",label:`Live site: ${i.site}.`}):e.siteSkipped?o.push({id:"site",evidence:"observed",status:"ok",label:"No live site, by choice. Grum checks the code only."}):i.role==="owner"&&(n.push("needs-site"),o.push({id:"site",evidence:"observed",status:"missing",label:"No live site is saved, so Grum checks the code but not the deployed pages."}),r.push({kind:"set-site",explanation:e.siteHints.length?"Ask the person for the live site's URL. Offer the URLs in input.uncertain, found in this repository and not checked, and let them type another. Pass --site with the answer, or none when there is no live site.":"Ask the person for the live site's URL. First look in the repository (README, deploy config, metadata) for a production URL to offer. Pass --site with the answer, or none when there is no live site.",input:{flag:"--site",...e.siteHints.length?{uncertain:e.siteHints}:{},command:Se(e,t)}})),e.extension===null?o.push({id:"extension",evidence:"unknown",status:"unknown",label:"Could not tell whether the Chrome extension is connected. The server is the source of that check, and it is not proof the extension is in the browser you have open."}):e.extension.connected?o.push({id:"extension",evidence:"observed",status:"ok",label:"Grum's server shows the Chrome extension connected for this workspace. That is not proof it is installed in the browser you have open."}):(n.push("needs-extension"),o.push({id:"extension",evidence:"observed",status:"missing",label:"The Chrome extension is not connected. Pages behind a sign-in need it. Code checks do not."}),r.push({kind:"connect-extension",explanation:"Open Grum's extension page in the Chrome profile the person uses, install or load the extension, and choose Connect for this workspace.",input:{url:e.extensionUrl}}));let m=!e.localSaved&&!e.localSkipped;if(e.localSkipped&&!e.localSaved)o.push({id:"local-app",evidence:"observed",status:"ok",label:"No local app URL was saved, by choice. A later review picture will need one."});else if(e.localSaved&&e.localStatus===void 0)o.push({id:"local-app",evidence:"unknown",status:"unknown",label:`A local app URL is saved (${e.localSaved}) and was not probed on this run.`});else if(e.localSaved&&e.localStatus!==null&&e.localStatus!==void 0&&e.localStatus<500)o.push({id:"local-app",evidence:"observed",status:"ok",label:`Local app answering at ${e.localSaved}.`});else if(m||e.localSaved){n.push("needs-local-app"),o.push({id:"local-app",evidence:"observed",status:"missing",label:e.localSaved?`Local app not answering at ${e.localSaved}.`:"No local app URL is saved, so a review cannot show how a change renders until one is set."});let g=e.localSuggestions.filter(h=>h.verified),S=e.localSuggestions.filter(h=>!h.verified).map(h=>h.url);r.push({kind:"set-local-app",explanation:e.localSaved?"The saved local app is not answering. Ask the person whether to start it (offer to run its dev command) or use another URL. Pass --local-url with a page that answers, or none to skip.":g.length||S.length?"Ask the person where the app runs locally while they edit. Offer input.choices (they answered just now) and input.uncertain (seen in the repository, not running), and let them type another. When the one they pick is not running, offer to start the dev command. Pass --local-url with their answer, or none to skip.":"Ask the person where the app runs locally while they edit. First look in the repository (package.json scripts, README, docker-compose, framework config) for a localhost URL or an HTML entry file to offer. When it is not running, offer to start the dev command. Pass --local-url with their answer, or none to skip.",input:{flag:"--local-url",...g.length?{choices:g.map(h=>({id:h.url,label:h.url}))}:{},...S.length?{uncertain:S}:{},command:Se(e,t)}})}return be(e,t,{open:n,actions:r,checks:o,fault:s})}function KC(e,t){return e==="cursor"?`In Cursor, ${Ye} Then start a new chat so the tools load. Until then, use the CLI commands in this guide.`:e==="claude"?`In Claude Code, ${Al} Until then, use the CLI commands in this guide.`:e==="codex"?`In Codex, ${Cs} Then ${Nl} Start a new chat so Codex reloads them.`:`Run setup to write Grum's files for ${t}, then complete the editor step it reports. File presence is not the same as tools loaded.`}function XC(e,t,n,r,o,s){if(t.agent==="generic"){s.push({id:"enforcement",evidence:"observed",status:"missing",label:"Automatic enforcement is unavailable for this agent. Checks and review still run when asked. Hooks are not installed."});return}if(!e.adapterWritten){r.push("needs-host-action"),s.push({id:"adapter",evidence:"observed",status:"missing",label:`Grum is not configured in ${n} yet.`}),o.push({kind:"enable-host",explanation:`Run setup to write Grum's files for ${n}, then complete the editor step it reports. File presence is not the same as tools loaded.`,input:{command:Se(e,{...t,probe:!1})}});return}s.push({id:"adapter",evidence:"observed",status:"ok",label:`Grum's files are written for ${n}. That alone does not mean the tools are loaded.`});let i=e.tools==="on"||e.tools==="off";s.push({id:"tools",evidence:e.tools==="unknown"?"unknown":"observed",status:e.tools==="on"?"ok":e.tools==="off"?"missing":"unknown",label:e.tools==="on"?`Grum's tools are loaded in ${n}.`:e.tools==="off"?`Grum's tools are not on in ${n}.`:`Could not confirm that Grum's tools are loaded in ${n}.`});let l=e.hooks==="active"||e.hooks==="inactive";e.hooks!=="not-applicable"&&s.push({id:"hooks",evidence:l?"observed":"unknown",status:e.hooks==="active"?"ok":e.hooks==="inactive"?"missing":"unknown",label:e.hooks==="active"?`Grum's hooks have run in ${n}.`:e.hooks==="inactive"?`Grum's hooks have not been allowed in ${n}.`:`Could not confirm that Grum's hooks are active in ${n}.`}),!(t.agent==="codex"?e.hooks==="active":e.tools==="on")&&(i||e.tools==="unknown"||e.hooks!=="not-applicable")&&(r.push("needs-host-action"),o.push({kind:"enable-host",explanation:KC(t.agent,n),input:{command:Se(e,t,[],!0)}}))}function ZC(e,t,n,r){let{createApp:o,install:s}=n.starter.commands,i=Se(e,{...t,probe:!1}),l=(c,u)=>({label:u,action:{kind:"run-commands",explanation:"Run input.commands in order from the repository root, with stdin closed. They ask nothing. Do not delete or move files to make them work: when one refuses because the folder already has files in it, tell the person and stop. When they finish, run the same command again.",input:{commands:c,command:i}}});if(n.state==="no-app")return l([...o,...s],`This folder has no app yet, and the ${n.kit.name} starter needs one.`);if(n.state==="not-installed")return l(s,`${n.kit.name} is not installed in this app yet.`);if(n.state==="not-written")return{label:`The contract's tokens are not written into ${n.starter.tokensFile} yet.`,action:{kind:"run-commands",explanation:r==="owner"?"Run setup. It writes the contract's tokens into the app and adds DESIGN.md, and changes nothing else.":"An owner of this workspace has to run setup to write the contract's tokens into the app. Nothing was changed.",input:{commands:[i],command:i}}};let a=n.defaultBranch??"the default branch";return{label:`The starter is written, but not on ${a} on GitHub, where Grum's scan reads it.`,action:{kind:"commit-and-push",explanation:`Grum scans ${a} on GitHub, so the work has to be there first. Run \`${e.cli} check\`: it should report no drift. Then tell the person what you will commit (the app, ${n.starter.tokensFile} and DESIGN.md) and that you will push it to ${a}, and ask. Only with their yes: commit, push to ${a}, then run the same command again. If the work is on another branch, ask whether to merge it into ${a}. Never force-push.`,input:{...n.defaultBranch?{branch:n.defaultBranch}:{},command:i}}}}function be(e,t,n){let r=WC.filter(c=>n.open.includes(c)),o=r[0]??"ready",s=Sc(e),i=o==="ready"?"ready":n.fault?"error":"action-needed",l=[...n.checks],a=YC(e);return a&&l.push(a),{schema:th,state:o,open:r,server:e.serverOrigin?{origin:e.serverOrigin,label:pt(e.serverOrigin)}:null,repository:{name:e.repoName},project:s?{id:s.id,name:s.name}:null,checks:l,actions:n.actions,exit:i}}function YC(e){if(!e.sourceRead)return null;let t=ao(io(e.sourceRead.ms,e.sourceRead.stalled));return t?{id:"source-read",evidence:"observed",status:e.sourceRead.stalled?"missing":"ok",label:t}:null}function QC(e,t){return{schema:th,state:"blocked",open:["blocked"],server:e?{origin:e,label:pt(e)}:null,repository:null,project:null,checks:[{id:"request",evidence:"observed",status:"blocked",label:t}],actions:[{kind:"fix-error",explanation:t}],exit:"error"}}function Se(e,t,n=[],r=!1){let o=[e.cli,r||t.probe?"doctor":"setup","--agent",t.agent,"--json"];t.root&&o.push("--root",t.root),t.projectId&&o.push("--project",t.projectId),t.create&&o.push("--create"),t.name&&o.push("--name",t.name),t.siteProvided&&o.push("--site",t.site??"none"),t.localProvided&&o.push("--local-url",t.localUrl??"none");for(let s of n)o.includes(s)||o.push(s);return o.join(" ")}var oh=[" @@@@@@@ @@@@@@@","@@@@@@@@@ @@@@@@@@@","@@@@@@@@@ @@@@@@@@@"," @@@@@@@@@@@@@@@@"," @@@@@@ "," @@@@@@@@@@@@@@@@","@@@@@@@@@ @@@@@@@@@","@@@@@@@@@ @@@@@@@@@"," @@@@@@@ @@@@@@@"],eR=Math.max(...oh.map(e=>e.length)),tR=[" .d8888b."," d88P Y88b","888 888","888 888d888 888 888 88888b.d88b.",'888 88888 888P" 888 888 888 "888 "88b',"88 888 888 888 888 888 888 888"," 88b d88P 888 Y88b 888 888 888 888",' "Y8888P88 888 "Y88888 888 888 888'],sh=oh.map((e,t)=>{let n=tR[t-1];return n?`${e.padEnd(eR)} ${n}`:e}).join(`
|
|
143
|
+
`,n);n=i<0?e.length:i;continue}if(s==="/"&&e[n+1]==="*"){let i=e.indexOf("*/",n+2);n=i<0?e.length:i+2;continue}t+=s,n++}try{return JSON.parse(t.replace(/,(\s*[}\]])/g,"$1"))}catch{return null}}function _C(e){return e==="package.json"?"":e.slice(0,-13)}function OC(e){let t=bc(e);if(!t||typeof t!="object")return[];let n=t,r=[];for(let o of["dependencies","devDependencies","peerDependencies"]){let s=n[o];s&&typeof s=="object"&&r.push(...Object.keys(s))}return r}function jC(e){let t=new Map,n=[...e.keys()].filter(o=>!qm(o)).sort(),r=0;for(let o of n){if(r>=TC)break;let s=e.get(o);if(!s)continue;r++;let i=bc(s),l=i&&typeof i=="object"?i.name:null;typeof l!="string"||!l||t.has(l)||t.set(l,_C(o))}return t}function yc(e,t){let n=`${e}/`;return t.some(r=>!qm(r.path)&&r.path.startsWith(n)&&Hm.test(r.path))}function oi(e){let t=e.lastIndexOf("/");return t<0?"":e.slice(0,t)}function cr(e,t){let n=[...oi(e)?oi(e).split("/"):[],...t.split("/")],r=[];for(let o of n)if(!(!o||o===".")){if(o===".."){if(!r.length)return null;r.pop();continue}r.push(o)}return r.join("/")}function Bm(e,t){let n=Je(t);return n?e!==n&&!e.startsWith(`${n}/`):!1}function Wm(e){if(!e||e.startsWith(".")||e.startsWith("/"))return null;let t=e.split("/");return e.startsWith("@")?t.length>=2?`${t[0]}/${t[1]}`:null:t[0]||null}function MC(e,t){let n=Hm.test(e)?oi(e):e;for(;n;){if(t.has(n))return n;let r=n.lastIndexOf("/");if(r<0)break;n=n.slice(0,r)}return t.has("")?"":null}function ri(e,t,n,r){if(!e||!Bm(e,t))return null;let o=MC(e,n);return!o||!Bm(o,t)?null:yc(o,r)?o:null}function PC(e){return[...e.matchAll(/["']([^"']+)["']/g)].map(t=>t[1]).filter(Boolean)}function DC(e){return oi(e)}function Jm(e,t,n,r){let o=Je(e);if(!o)return[];let s=jC(n),i=new Set([...s.values()].filter(Boolean)),l=new Map,a=(f,m)=>{!f||l.has(f)||l.set(f,m)};if(n.get(`${o}/package.json`)){let f=new Set([o]),m=[{folder:o,via:null}];for(;m.length;){let g=m.shift(),S=n.get(g.folder?`${g.folder}/package.json`:"package.json");if(S)for(let h of OC(S)){let b=s.get(h);!b||f.has(b)||(f.add(b),yc(b,t)&&a(b,g.via?`${o} depends on ${h} through ${g.via}`:`${o} depends on ${h}`),m.push({folder:b,via:h}))}}}let u=[...r.keys()].filter(f=>(f==="tsconfig.json"||f.endsWith("/tsconfig.json")||/(^|\/)tsconfig\.[^/]+\.json$/.test(f))&&(f===o||f.startsWith(`${o}/`)||!f.includes("/"))),p=new Set,d=u.filter(f=>f===`${o}/tsconfig.json`||f.startsWith(`${o}/tsconfig`));for(d.length||d.push(...u.filter(f=>f.startsWith(`${o}/`)));d.length;){let f=d.shift();if(p.has(f))continue;p.add(f);let m=r.get(f);if(!m)continue;let g=bc(m);if(!g||typeof g!="object")continue;let S=g,h=S.extends,b=Array.isArray(h)?h:[h];for(let x of b){if(typeof x!="string")continue;let v=x.startsWith(".")?cr(f,x):(()=>{let $=Wm(x),A=$?s.get($):void 0;if(!A)return null;let E=x.slice($.length).replace(/^\//,"");return E?`${A}/${E}`:A})();v&&r.has(v)&&d.push(v)}let k=typeof S.compilerOptions?.baseUrl=="string"?cr(f,S.compilerOptions.baseUrl):DC(f),y=S.compilerOptions?.paths;if(y&&typeof y=="object"&&k!=null){for(let x of Object.values(y))if(Array.isArray(x))for(let v of x){if(typeof v!="string")continue;let $=cr(`${k}/x`,v.replace(/\*.*$/,"").replace(/\/$/,"")||".");$&&a(ri($,o,i,t),`${o} references ${$} from tsconfig`)}}if(Array.isArray(S.references))for(let x of S.references){let v=x&&typeof x=="object"&&typeof x.path=="string"?x.path:null;if(!v)continue;let $=cr(f,v);$&&a(ri($,o,i,t),`${o} references ${$} from tsconfig`)}}for(let[f,m]of r){if(!f.startsWith(`${o}/`))continue;let g=/\.(css|scss|less)$/i.test(f),S=/(^|\/)tailwind\.config\./.test(f),h=[];if(g)for(let b of m.matchAll(/@(?:import|use|forward|source|config|plugin)\s+(?:url\(\s*)?["']([^"']+)["']/g))h.push(b[1]);if(S)for(let b of["content","presets"]){let k=new RegExp(`${b}\\s*:\\s*\\[([\\s\\S]*?)\\]`).exec(m);k?.[1]&&h.push(...PC(k[1]))}for(let b of h){if(b.startsWith(".")){let x=cr(f,b),v=x?ri(x,o,i,t):null;a(v,`${o} imports ${b}`);continue}let k=Wm(b),y=k?s.get(k):void 0;y&&yc(y,t)&&a(y,`${o} imports ${b}`)}if(/\.(tsx?|jsx?|mjs|cjs|vue|svelte|astro)$/i.test(f))for(let b of m.matchAll(/(?:from\s+|import\s*\(\s*|require\(\s*)["'](\.[^"']+)["']/g)){let k=b[1],y=cr(f,k),x=y?ri(y,o,i,t):null;a(x,`${o} imports ${k}`)}}return[...l.entries()].map(([f,m])=>({folder:f,reason:m})).sort((f,m)=>f.folder<m.folder?-1:1)}function Km(e){if(e.flag!==void 0)return Je(e.flag);let t=Je(e.config??"");return t||Je(e.projectRoot??"")}function si(e){if(e.flag!==void 0)return Je(e.flag);let t=hc.relative(e.repoDir,e.cwd);return!t||t==="."||t.startsWith("..")||hc.isAbsolute(t)?"":Je(t.split(hc.sep).join("/"))}var Xm=4e7,wc=5e3,ii=15e3;function io(e,t){let n=Math.max(1,Math.round(e/1e3));return t||e>=ii?{kind:"limit",seconds:n}:e>=wc?{kind:"slow",seconds:n}:{kind:"ok"}}function ao(e){return e.kind==="slow"?`Reading source took ${e.seconds}s. Grum skips videos and large images by name; a huge source tree or a slow disk can still take a while.`:e.kind==="limit"?"Reading source stopped at the 15s limit. Grum skips videos and large images by name, so this is a huge source tree, a slow disk, or a stalled git list. This used what it had so far.":null}var LC=/(^|\/)\.grum\//;function kc(e){return!Dr.test(`${e}/`)&&!LC.test(`${e}/`)}function xc(e,t){return cf(e,t)}function Zm(e){let t=Math.min(e.length,8e3);for(let n=0;n<t;n++)if(e[n]===0)return!0;return!1}var NC=/\.(css|scss|sass|less|tsx?|jsx?|mjs|cjs|vue|svelte|astro|html?)$/i;function Ym(e){return NC.test(e)}function Qm(e,t,n){let r=new Map(e);for(let o of t){let s=Xt(o.path,n);s===null||!kc(s.split("/").slice(0,-1).join("/")||".")||(o.content===null?r.delete(s):xc(s,o.content.length)&&r.set(s,o.content))}return r}function eh(e){let t=e.split("\0").filter(Boolean),n=[];for(let r=0;r<t.length;r++){let o=t[r];if(/^[RC]/.test(o)){let i=t[++r],l=t[++r];o.startsWith("R")&&i&&n.push({path:i,deleted:!0}),l&&n.push({path:l,deleted:!1});continue}let s=t[++r];s&&n.push({path:s,deleted:o.startsWith("D")})}return n}function ai(e){let t=/^(?:git@|ssh:\/\/git@|https?:\/\/(?:[^@/]+@)?)github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i.exec(e.trim());return t?`${t[1]}/${t[2]}`:null}var th=1,FC=["cursor","codex","claude","generic"],ur={cursor:"Cursor",codex:"Codex",claude:"Claude Code"};var UC={ready:0,"action-needed":2,error:1},GC="https://nodejs.org/en/download",li=22,zC="Give the person the URL in this result and ask them to open it. That Grum page checks the repository and walks them through it: Reconnect GitHub, then, if still blocked, Grum's Manage Access page on GitHub, where they grant the organization that owns the repository (or click Request when an org owner must approve). It says when Grum can read the repository. Grum is an OAuth app named Grum, not a GitHub App: do not send the person anywhere else on GitHub to hunt for it. Then run the same command again.";function BC(e){return e.scan==="failed"&&qn(e.scanMessage??"")}var WC=["blocked","needs-sign-in","needs-github","needs-workspace-create","needs-project-choice","needs-starter","needs-host-action","scan-running","needs-site","needs-extension","needs-local-app","ready"];function nh(e){return UC[e.exit]}function vc(e){let t=e.serverOrigin??"",n=d=>({ok:!1,result:QC(t,d)}),r=(e.agent??[]).map(d=>d.trim().toLowerCase());if(r.length!==1||!VC(r[0]))return n("Pass --agent with one of cursor, codex, claude or generic. generic can set up and check, and reports that automatic enforcement is unavailable.");let o=r[0];if((e.project?.length??0)>1)return n("Pass --project once, with the project id from the choices.");let s=e.project?.[0]?.trim()||null;if(e.project&&!s)return n("Pass --project with the project id from the choices.");let i=!!e.create,l=!!e.remove;if(i&&s)return n("Pass either --create or --project, not both.");if(i&&l)return n("Pass either --create or --remove, not both.");if(l&&e.switchServer)return n("Removing Grum from this repo is separate from switching servers. Run one, then the other.");if((e.name?.length??0)>1||(e.site?.length??0)>1)return n("Pass --name and --site at most once.");if((e.localUrl?.length??0)>1||(e.root?.length??0)>1)return n("Pass --local-url and --root at most once.");let a;if(e.site===void 0)a=void 0;else if(/^(none|skip)$/i.test(e.site[0]?.trim()??""))a=null;else{let d=gn(e.site[0]??"");if(!d)return n("Pass --site as an https URL or none. The live site is optional.");a=d}let c,u=null;if(e.localUrl===void 0)c=void 0;else{let d=HC(e.localUrl[0]??"");if(!d.ok)return n("Pass --local-url as an http(s) URL, a file in the repo such as ./index.html, or none.");c=d.url,u=d.kind}let p=(e.name?.[0]??"").trim().slice(0,80)||null;return e.probe&&(i||l||s||e.switchServer)?n("doctor only reports readiness. Run setup --agent to create, choose, switch or remove."):{ok:!0,request:{agent:o,projectId:s,create:i,remove:l,name:p,site:a,siteProvided:e.site!==void 0,localUrl:c,localProvided:e.localUrl!==void 0,localKind:u,root:e.root?.[0]?.trim()||null,switchServer:!!e.switchServer,dropPreCommit:!!e.dropPreCommit,probe:!!e.probe}}}function Ae(e,t){let n=t.probe?null:qC(e,t);return{result:JC(e,t),mutation:n}}function rh(e,t){let n=ur[t.agent==="generic"?"cursor":t.agent],r=t.dropPreCommit?" The pre-commit check was removed.":" The pre-commit check was left as it was.";return be(e,t,{open:[],fault:!1,checks:[{id:"adapter",evidence:"observed",status:"ok",label:`Removed Grum from ${n}.${r} This computer's sign-in and the Grum workspace were kept.`}],actions:[]})}function VC(e){return FC.includes(e??"")}function HC(e){let t=e.trim();if(/^(none|skip)$/i.test(t))return{ok:!0,url:null,kind:"none"};if(/^https?:\/\//i.test(t))try{let n=new URL(t);return n.username||n.password?{ok:!1}:{ok:!0,url:n.href,kind:"url"}}catch{return{ok:!1}}return!t||t.includes("..")||t.startsWith("/")||t.includes("\\")||/^[a-z][a-z0-9+.-]*:/i.test(t)?{ok:!1}:{ok:!0,url:t,kind:"file"}}function Sc(e){return!e.selectedId||!e.projects?null:e.projects.find(t=>t.id===e.selectedId)??null}function qC(e,t){if(!e.runtimeOk)return null;if(!!(e.repoServer&&e.repoServer!==e.serverOrigin))return t.switchServer?{kind:"switch-server"}:null;if(t.remove&&t.agent!=="generic")return{kind:"remove",agent:t.agent,dropPreCommit:t.dropPreCommit};if(e.offline||!e.signedIn||e.origin!=="github"||e.github==="missing"||e.github==="no-access")return null;let r=e.projects;if(!r)return null;let o=Sc(e);if(t.projectId&&r.some(l=>l.id===t.projectId)&&o?.id!==t.projectId)return{kind:"select",projectId:t.projectId};let s=e.kit;if(s?.needsRepo&&t.projectId===s.project.id&&r.length===0&&s.project.role==="owner"&&e.repoName)return{kind:"link-repo",projectId:s.project.id,repo:e.repoName};if(t.create&&r.length===0)return{kind:"create",name:t.name,site:t.siteProvided?t.site??null:null};if(!o)return null;let i=s&&s.project.id===o.id&&s.state!=="pushed";return e.scan==="needed"&&o.role==="owner"&&i?s.state==="not-written"?{kind:"write-starter",projectId:o.id}:null:t.agent!=="generic"&&!e.adapterWritten&&!i?{kind:"install",agent:t.agent}:(e.scan==="needed"||BC(e))&&o.role==="owner"?{kind:"start-scan",projectId:o.id}:e.scan!=="done"?null:t.siteProvided&&o.role==="owner"&&(t.site??null)!==(o.site??null)?{kind:"set-site",projectId:o.id,site:t.site??null}:t.siteProvided&&t.site===null&&o.role==="owner"&&!o.site&&!e.siteSkipped?{kind:"skip-site",projectId:o.id}:t.localProvided&&(t.localUrl!==e.localSaved||t.localUrl===null&&!e.localSkipped)?{kind:"set-local",url:t.localUrl??null}:null}function JC(e,t){let n=[],r=[],o=[],s=!1,i=Sc(e),l=ur[t.agent==="generic"?"cursor":t.agent];if(o.push({id:"runtime",evidence:"observed",status:e.runtimeOk?"ok":"missing",label:e.runtimeOk?`Node.js ${e.runtimeVersion} can run Grum.`:`Node.js ${e.runtimeVersion||"is missing"}; Grum needs ${li} or newer.`}),!e.runtimeOk)return n.push("blocked"),r.push({kind:"install-runtime",explanation:`Ask the person to install Node.js ${li} or newer from the browser, then restart the editor and run this again. Do not ask them to type a command.`,input:{url:GC,command:Se(e,t)}}),be(e,t,{open:n,actions:r,checks:o,fault:!1});let a=!!(e.repoServer&&e.repoServer!==e.serverOrigin);if(o.push({id:"server",evidence:"observed",status:a?"blocked":"ok",label:a?`This repo is set up for ${pt(e.repoServer)} (${e.repoServer}), and this Grum is ${pt(e.serverOrigin)} (${e.serverOrigin}).`:`Grum ${pt(e.serverOrigin)} (${e.serverOrigin}).`}),a)return n.push("blocked"),r.push({kind:"switch-server",explanation:"Ask before switching. Nothing was changed. Switching sets the previous server's local Grum files aside and sets this repo up here.",input:{flag:"--switch-server",command:Se(e,{...t,probe:!1},["--switch-server"])}}),be(e,t,{open:n,actions:r,checks:o,fault:!1});if(t.remove)return t.agent==="generic"&&(s=!0,n.push("blocked"),r.push({kind:"fix-error",explanation:`A generic agent has no Grum files to remove. To sign out this computer, run ${e.cli} logout.`})),be(e,t,{open:n,actions:r,checks:o,fault:s});if(e.offline)return s=!0,n.push("blocked"),o.push({id:"server-reach",evidence:"observed",status:"blocked",label:e.offlineMessage??"Grum could not be reached. Nothing was marked ready."}),r.push({kind:"fix-error",explanation:"Grum could not be reached, so this run is not ready. The connection is down or too slow (the CLI waits 15 seconds). Try the same command again once it answers. An offline check can still use a contract already saved on this computer, and that result is not current.",input:{command:Se(e,t)}}),be(e,t,{open:n,actions:r,checks:o,fault:s});if(e.origin==="absent"||e.origin==="unsupported")return s=!0,n.push("blocked"),o.push({id:"repository",evidence:"observed",status:"blocked",label:e.origin==="absent"?"This folder has no GitHub origin remote.":"This folder's origin remote is not a GitHub repository Grum can connect."}),r.push({kind:"fix-error",explanation:e.origin==="absent"?"Grum did not invent a git remote. Add the repository's GitHub origin, then run this again.":"Grum only connects a github.com origin. It did not change the remote."}),be(e,t,{open:n,actions:r,checks:o,fault:s});if(o.push({id:"sign-in",evidence:"observed",status:e.signedIn?"ok":"missing",label:e.signedIn?"Signed in on this computer.":e.signInExpired?"The sign-in on this computer expired or was revoked.":"This computer is not signed in to Grum."}),!e.signedIn){n.push("needs-sign-in");let g=e.pendingLogin;return r.push({kind:"approve-sign-in",explanation:g?`Open this Grum page, sign in, and approve code ${g.code}. Then run the same command again.`:"Run setup so Grum can start a browser sign-in. Doctor does not start one. The next result has the code and the page.",input:{...g?{url:g.url,code:g.code,expiresAt:g.expiresAt}:{},command:Se(e,{...t,probe:!1})}}),be(e,t,{open:n,actions:r,checks:o,fault:s})}let c=e.github==="no-access"?e.repoName:null;if(e.github==="missing"||e.github==="no-access")return n.push("needs-github"),o.push({id:"github",evidence:"observed",status:"missing",label:e.github==="no-access"?`This GitHub login cannot read ${c??"that repository"}: it is not found or access is denied.`:e.githubMessage??"GitHub is not connected for this account."}),r.push({kind:"connect-github",explanation:zC,input:{url:`${e.serverOrigin}${so(void 0,c??void 0)}`,command:Se(e,t)}}),be(e,t,{open:n,actions:r,checks:o,fault:s});if(e.projects===null)return s=!0,n.push("blocked"),o.push({id:"project",evidence:"unknown",status:"unknown",label:"Grum did not return this repository's workspaces."}),r.push({kind:"fix-error",explanation:"Run the same command again. No workspace was created.",input:{command:Se(e,t)}}),be(e,t,{open:n,actions:r,checks:o,fault:s});e.staleProjectId&&!i&&o.push({id:"project",evidence:"observed",status:"blocked",label:`The saved project ${e.staleProjectId} is not a workspace this account can use.`});let u=e.kit?.needsRepo===!0&&t.projectId===e.kit.project.id;if(t.projectId&&!u&&!e.projects.some(g=>g.id===t.projectId)){let g=e.projects.map(S=>({id:S.id,label:S.name}));return g.length||(s=!0),n.push(g.length?"needs-project-choice":"blocked"),r.push({kind:g.length?"choose-project":"fix-error",explanation:g.length?"That project id is not one this account can use for this repository. Ask which of these to use.":"That project id is not a workspace this account can use, and this repository has none yet.",input:g.length?{flag:"--project",choices:g,command:Se(e,t)}:void 0}),be(e,t,{open:n,actions:r,checks:o,fault:s})}let p=u?e.kit:null;if(e.projects.length===0&&p){let g=p.project.name;return p.project.role!=="owner"?(s=!0,n.push("blocked"),o.push({id:"project",evidence:"observed",status:"blocked",label:`Only an owner of ${g} can connect a repository to it.`}),r.push({kind:"fix-error",explanation:`${e.repoName} is not connected to ${g}, and only an owner of that workspace can connect it. Ask an owner. Nothing was changed.`}),be(e,t,{open:n,actions:r,checks:o,fault:s})):(n.push("needs-workspace-create"),o.push({id:"project",evidence:"observed",status:"missing",label:`${e.repoName} is not connected to the ${g} workspace yet.`}),r.push({kind:"link-workspace",explanation:`Connect ${e.repoName} to the ${g} workspace, which started from ${p.kit.name}. The person's request to set up this project already authorized it: say that you are connecting it, then run setup. A readiness check never connects it.`,input:{command:Se(e,{...t,probe:!1})}}),be(e,t,{open:n,actions:r,checks:o,fault:s}))}if(e.projects.length===0)return n.push("needs-workspace-create"),o.push({id:"project",evidence:"observed",status:"missing",label:`No Grum workspace you can access is connected to ${e.repoName}.`}),r.push({kind:"create-workspace",explanation:t.create?`Creating a Grum workspace for ${e.repoName}.`:`Create a Grum workspace for ${e.repoName}. The person's explicit setup request already authorized this; state that you are creating it, then run setup with --create. A readiness check never creates one.`,input:{flag:"--create",command:Se(e,{...t,probe:!1,create:!0})}}),be(e,t,{open:n,actions:r,checks:o,fault:s});if(!i)return n.push("needs-project-choice"),o.push({id:"project",evidence:"observed",status:"missing",label:`Several workspaces use ${e.repoName}. None is selected.`}),r.push({kind:"choose-project",explanation:"Ask which workspace to use. Pass its id as --project. Do not choose the first.",input:{flag:"--project",choices:e.projects.map(g=>({id:g.id,label:g.name})),command:Se(e,t)}}),be(e,t,{open:n,actions:r,checks:o,fault:s});o.push({id:"project",evidence:"observed",status:"ok",label:`Linked to ${i.name} (${e.repoName}).`});let d=e.scan==="needed"&&e.kit?.project.id===i.id?e.kit:null;if(d&&d.state!=="pushed"){n.push("needs-starter");let g=ZC(e,t,d,i.role);return o.push({id:"starter",evidence:"observed",status:"missing",label:g.label}),r.push(g.action),be(e,t,{open:n,actions:r,checks:o,fault:s})}XC(e,t,l,n,r,o);let f=e.scan==="running"&&e.scanProgress?.status==="running"?e.scanProgress:null;if(e.scan==="failed"?(s=!0,n.push("blocked"),o.push({id:"scan",evidence:"observed",status:"blocked",label:e.scanMessage??"The first scan failed."}),r.push({kind:"fix-error",explanation:"The scan failed. Report that and stop. Do not start another scan unless the person asks."})):e.scan==="running"||e.scan==="needed"?(n.push("scan-running"),o.push({id:"scan",evidence:"observed",status:"missing",label:e.scan==="running"?f?`Grum is still reading ${e.repoName}: ${f.label} (${f.percent}%).`:`Grum is still reading ${e.repoName}.`:`Grum has not finished reading ${e.repoName}.`}),r.push({kind:"wait-for-scan",explanation:zm(f),input:{command:Se(e,t),...f?mc(f):{}}})):e.scan==="done"?o.push({id:"code-check",evidence:"observed",status:"ok",label:"Code checks can run against the current contract. That is separate from capture."}):o.push({id:"code-check",evidence:"unknown",status:"unknown",label:"No contract has been checked on this run."}),e.scan!=="done")return be(e,t,{open:n,actions:r,checks:o,fault:s});t.siteProvided&&i.role!=="owner"&&(t.site??null)!==(i.site??null)?o.push({id:"site",evidence:"observed",status:"blocked",label:"Only an owner can change the live site, so it was left as it is."}):i.site?o.push({id:"site",evidence:"observed",status:"ok",label:`Live site: ${i.site}.`}):e.siteSkipped?o.push({id:"site",evidence:"observed",status:"ok",label:"No live site, by choice. Grum checks the code only."}):i.role==="owner"&&(n.push("needs-site"),o.push({id:"site",evidence:"observed",status:"missing",label:"No live site is saved, so Grum checks the code but not the deployed pages."}),r.push({kind:"set-site",explanation:e.siteHints.length?"Ask the person for the live site's URL. Offer the URLs in input.uncertain, found in this repository and not checked, and let them type another. Pass --site with the answer, or none when there is no live site.":"Ask the person for the live site's URL. First look in the repository (README, deploy config, metadata) for a production URL to offer. Pass --site with the answer, or none when there is no live site.",input:{flag:"--site",...e.siteHints.length?{uncertain:e.siteHints}:{},command:Se(e,t)}})),e.extension===null?o.push({id:"extension",evidence:"unknown",status:"unknown",label:"Could not tell whether the Chrome extension is connected. The server is the source of that check, and it is not proof the extension is in the browser you have open."}):e.extension.connected?o.push({id:"extension",evidence:"observed",status:"ok",label:"Grum's server shows the Chrome extension connected for this workspace. That is not proof it is installed in the browser you have open."}):(n.push("needs-extension"),o.push({id:"extension",evidence:"observed",status:"missing",label:"The Chrome extension is not connected. Pages behind a sign-in need it. Code checks do not."}),r.push({kind:"connect-extension",explanation:"Open Grum's extension page in the Chrome profile the person uses, install or load the extension, and choose Connect for this workspace.",input:{url:e.extensionUrl}}));let m=!e.localSaved&&!e.localSkipped;if(e.localSkipped&&!e.localSaved)o.push({id:"local-app",evidence:"observed",status:"ok",label:"No local app URL was saved, by choice. A later review picture will need one."});else if(e.localSaved&&e.localStatus===void 0)o.push({id:"local-app",evidence:"unknown",status:"unknown",label:`A local app URL is saved (${e.localSaved}) and was not probed on this run.`});else if(e.localSaved&&e.localStatus!==null&&e.localStatus!==void 0&&e.localStatus<500)o.push({id:"local-app",evidence:"observed",status:"ok",label:`Local app answering at ${e.localSaved}.`});else if(m||e.localSaved){n.push("needs-local-app"),o.push({id:"local-app",evidence:"observed",status:"missing",label:e.localSaved?`Local app not answering at ${e.localSaved}.`:"No local app URL is saved, so a review cannot show how a change renders until one is set."});let g=e.localSuggestions.filter(h=>h.verified),S=e.localSuggestions.filter(h=>!h.verified).map(h=>h.url);r.push({kind:"set-local-app",explanation:e.localSaved?"The saved local app is not answering. Ask the person whether to start it (offer to run its dev command) or use another URL. Pass --local-url with a page that answers, or none to skip.":g.length||S.length?"Ask the person where the app runs locally while they edit. Offer input.choices (they answered just now) and input.uncertain (seen in the repository, not running), and let them type another. When the one they pick is not running, offer to start the dev command. Pass --local-url with their answer, or none to skip.":"Ask the person where the app runs locally while they edit. First look in the repository (package.json scripts, README, docker-compose, framework config) for a localhost URL or an HTML entry file to offer. When it is not running, offer to start the dev command. Pass --local-url with their answer, or none to skip.",input:{flag:"--local-url",...g.length?{choices:g.map(h=>({id:h.url,label:h.url}))}:{},...S.length?{uncertain:S}:{},command:Se(e,t)}})}return be(e,t,{open:n,actions:r,checks:o,fault:s})}function KC(e,t,n){return e==="cursor"?`In Cursor, ${Ye} Then start a new chat so the tools load. Until then, use the CLI commands in this guide.`:e==="claude"?`In Claude Code, ${Al} Until then, use the CLI commands in this guide.`:e==="codex"?n==="unknown"?"Grum has not confirmed a successful hook run in Codex, so hook status is unknown. This does not show whether they are enabled. If any Grum command is off in Settings > Hooks, allow it; if they are on, check the latest run for a command error. Start a new chat after changing a hook.":`In Codex, ${Cs} Then ${Nl} Start a new chat so Codex reloads them.`:`Run setup to write Grum's files for ${t}, then complete the editor step it reports. File presence is not the same as tools loaded.`}function XC(e,t,n,r,o,s){if(t.agent==="generic"){s.push({id:"enforcement",evidence:"observed",status:"missing",label:"Automatic enforcement is unavailable for this agent. Checks and review still run when asked. Hooks are not installed."});return}if(!e.adapterWritten){r.push("needs-host-action"),s.push({id:"adapter",evidence:"observed",status:"missing",label:`Grum is not configured in ${n} yet.`}),o.push({kind:"enable-host",explanation:`Run setup to write Grum's files for ${n}, then complete the editor step it reports. File presence is not the same as tools loaded.`,input:{command:Se(e,{...t,probe:!1})}});return}s.push({id:"adapter",evidence:"observed",status:"ok",label:`Grum's files are written for ${n}. That alone does not mean the tools are loaded.`});let i=e.tools==="on"||e.tools==="off";s.push({id:"tools",evidence:e.tools==="unknown"?"unknown":"observed",status:e.tools==="on"?"ok":e.tools==="off"?"missing":"unknown",label:e.tools==="on"?`Grum's tools are loaded in ${n}.`:e.tools==="off"?`Grum's tools are not on in ${n}.`:`Could not confirm that Grum's tools are loaded in ${n}.`});let l=e.hooks==="active";e.hooks!=="not-applicable"&&s.push({id:"hooks",evidence:l?"observed":"unknown",status:e.hooks==="active"?"ok":"unknown",label:e.hooks==="active"?`Grum's hooks have run in ${n}.`:`Grum has not confirmed a successful hook run in ${n}; whether the hooks are enabled is unknown.`}),!(t.agent==="codex"?e.hooks==="active":e.tools==="on")&&(i||e.tools==="unknown"||e.hooks!=="not-applicable")&&(r.push("needs-host-action"),o.push({kind:"enable-host",explanation:KC(t.agent,n,e.hooks),input:{command:Se(e,t,[],!0)}}))}function ZC(e,t,n,r){let{createApp:o,install:s}=n.starter.commands,i=Se(e,{...t,probe:!1}),l=(c,u)=>({label:u,action:{kind:"run-commands",explanation:"Run input.commands in order from the repository root, with stdin closed. They ask nothing. Do not delete or move files to make them work: when one refuses because the folder already has files in it, tell the person and stop. When they finish, run the same command again.",input:{commands:c,command:i}}});if(n.state==="no-app")return l([...o,...s],`This folder has no app yet, and the ${n.kit.name} starter needs one.`);if(n.state==="not-installed")return l(s,`${n.kit.name} is not installed in this app yet.`);if(n.state==="not-written")return{label:`The contract's tokens are not written into ${n.starter.tokensFile} yet.`,action:{kind:"run-commands",explanation:r==="owner"?"Run setup. It writes the contract's tokens into the app and adds DESIGN.md, and changes nothing else.":"An owner of this workspace has to run setup to write the contract's tokens into the app. Nothing was changed.",input:{commands:[i],command:i}}};let a=n.defaultBranch??"the default branch";return{label:`The starter is written, but not on ${a} on GitHub, where Grum's scan reads it.`,action:{kind:"commit-and-push",explanation:`Grum scans ${a} on GitHub, so the work has to be there first. Run \`${e.cli} check\`: it should report no drift. Then tell the person what you will commit (the app, ${n.starter.tokensFile} and DESIGN.md) and that you will push it to ${a}, and ask. Only with their yes: commit, push to ${a}, then run the same command again. If the work is on another branch, ask whether to merge it into ${a}. Never force-push.`,input:{...n.defaultBranch?{branch:n.defaultBranch}:{},command:i}}}}function be(e,t,n){let r=WC.filter(c=>n.open.includes(c)),o=r[0]??"ready",s=Sc(e),i=o==="ready"?"ready":n.fault?"error":"action-needed",l=[...n.checks],a=YC(e);return a&&l.push(a),{schema:th,state:o,open:r,server:e.serverOrigin?{origin:e.serverOrigin,label:pt(e.serverOrigin)}:null,repository:{name:e.repoName},project:s?{id:s.id,name:s.name}:null,checks:l,actions:n.actions,exit:i}}function YC(e){if(!e.sourceRead)return null;let t=ao(io(e.sourceRead.ms,e.sourceRead.stalled));return t?{id:"source-read",evidence:"observed",status:e.sourceRead.stalled?"missing":"ok",label:t}:null}function QC(e,t){return{schema:th,state:"blocked",open:["blocked"],server:e?{origin:e,label:pt(e)}:null,repository:null,project:null,checks:[{id:"request",evidence:"observed",status:"blocked",label:t}],actions:[{kind:"fix-error",explanation:t}],exit:"error"}}function Se(e,t,n=[],r=!1){let o=[e.cli,r||t.probe?"doctor":"setup","--agent",t.agent,"--json"];t.root&&o.push("--root",t.root),t.projectId&&o.push("--project",t.projectId),t.create&&o.push("--create"),t.name&&o.push("--name",t.name),t.siteProvided&&o.push("--site",t.site??"none"),t.localProvided&&o.push("--local-url",t.localUrl??"none");for(let s of n)o.includes(s)||o.push(s);return o.join(" ")}var oh=[" @@@@@@@ @@@@@@@","@@@@@@@@@ @@@@@@@@@","@@@@@@@@@ @@@@@@@@@"," @@@@@@@@@@@@@@@@"," @@@@@@ "," @@@@@@@@@@@@@@@@","@@@@@@@@@ @@@@@@@@@","@@@@@@@@@ @@@@@@@@@"," @@@@@@@ @@@@@@@"],eR=Math.max(...oh.map(e=>e.length)),tR=[" .d8888b.",".d88P Y88b","888 888","888 888d888 888 888 88888b.d88b.",'888 88888 888P" 888 888 888 "888 "88b',"88 888 888 888 888 888 888 888","Y88b d88P 888 Y88b 888 888 888 888",' "Y8888P88 888 "Y8888Y" 888 888 888'],sh=oh.map((e,t)=>{let n=tR[t-1];return n?`${e.padEnd(eR)} ${n}`:e}).join(`
|
|
144
144
|
`),nR="\x1B[38;2;203;52;81m",rR="\x1B[38;2;196;160;0m",ci="\x1B[0m",ih=e=>`${nR}${e}${ci}`,oR=[{name:"check",blurb:"Check your changes against the design contract"},{name:"setup",blurb:"Sign in, link this repository, set up Cursor"}];function Et(e){return/GitHub rejected the token|GitHub session expired|Sign in with GitHub|Sign in to Grum with GitHub/.test(e)}function ah(e,t){let n=e.split("/")[0]??e;return[`Grum can't read ${e} with your GitHub login.`,`Fix it here: ${t}`,`That page reconnects GitHub and links to GitHub's Manage Access page, where you grant the ${n} organization (or request it from an owner).`,"Then run the same command again."].join(`
|
|
145
145
|
`)}function Cc(e,t){if(e!==401)return null;let n=typeof t.error=="string"?t.error:"";return t.code==="no_github"||t.code==="needs_repo_scope"||Et(n)?"github":"grum"}function lo(e){return[...oR,e?{name:"logout",blurb:"Sign out of Grum"}:{name:"login",blurb:"Sign in to Grum"}]}var lh="\u2191\u2193 move Enter select 1\u20133 jump q quit",ch=/^[A-Za-z0-9_-]{1,80}$/;function $c(e){if(!e||typeof e!="object")return null;let t=e,n=typeof t.repo=="string"?t.repo.trim():"",r=typeof t.workspace?.name=="string"?t.workspace.name.trim():"";if(!n||!r)return null;let o=t.workspace?.id,s=typeof t.site=="string"?t.site.trim():"";return{workspace:r,workspaceId:typeof o=="string"&&ch.test(o)?o:null,repo:n,site:s||null}}function sR(e){return e&&e.replace(/^https?:\/\//,"").replace(/\/$/,"")||null}function uh(e){let t=sR(e.site);return`${[e.workspace,e.repo,...t?[t]:[]].join(" \xB7 ")}`}function ph(e,t){return ch.test(t)?`${e.replace(/\/$/,"")}/${t}`:null}function dh(e,t){if(e===200||t.code==="not-scanned"){let o=$c(t.project);return o?{line:uh(o),workspaceId:o.workspaceId}:null}if(t.code!=="choose-project"||!Array.isArray(t.projects))return null;let n=t.projects.flatMap(o=>{let s=$c(o);return s?[s]:[]}),r=n[0];return r?{line:`Connected to ${n.map(o=>o.workspace).join(", ")} \xB7 ${r.repo}`,workspaceId:null}:null}function fh(e,t,n){if(!e||!t||e.apiOrigin!==n||e.project.repo.toLowerCase()!==t.toLowerCase())return null;let r=$c(e.project);return r?{line:uh(r),workspaceId:r.workspaceId}:null}var gh="\x1B[2m",iR=e=>`${gh}${e}${ci}`;function aR(e){return`${gh}\x1B]8;;${e}\x1B\\${e}\x1B]8;;\x1B\\${ci}`}function mh(e,t=null,n=!1,r=null){let o=[t,r].filter(a=>a),s=o.length?`${o.join(`
|
|
146
146
|
`)}
|
|
@@ -556,7 +556,7 @@ Review wasn't sent.`);let{cached:u}=c;if(i.expected&&i.expected.projectId!==u.pr
|
|
|
556
556
|
Review wasn't sent.`);let d=Zn(p);if(i.expected&&i.expected.observed!==d)return Ke("This change was edited since this check. Check again before sending it for review.");let f=Xn(u.requests,a).get(t.key);if(p.coverage==="pending"||p.coverage==="allowed"||p.coverage==="rejected"&&(!f?.observed||f.observed===d))return f?{ok:!0,state:f,captured:!1,created:!1}:Ke(`${t.key} is already ${p.coverage}.`);let m=await NA(e,u,l,p,i,o);if("failure"in m)return Ke(`${m.failure}
|
|
557
557
|
Review wasn't sent. Add --no-capture to send it without a picture.`);let{key:g,area:S,kind:h,title:b,found:k,foundColor:y,uses:x,places:v,input:$}=p,A=new FormData;A.set("request",JSON.stringify({projectId:u.project.id,issue:{key:g,area:S,kind:h,title:b,found:k,foundColor:y,uses:x,places:v,...$?{input:$}:{}},reason:n,branch:a,observed:d,locate:m.locate,provenance:{source:m.source,revision:ce(e.repoDir,["rev-parse","HEAD"])?.trim()||null,worktree:(G=>G===null?"unknown":G.trim()?"changed":"clean")(ce(e.repoDir,["status","--porcelain"]))}})),m.capture&&Bf(A,m.capture);let E=await he("/api/cli/requests",{method:"POST",token:l.token,body:A});if(E.status!==200){let G=typeof E.body.error=="string"?E.body.error:`Grum answered ${E.status}.`;return Ke(E.status===0?`${G} The request may not have been sent; check Grum's Review page before asking again.`:G)}let{created:M,...L}=E.body,P=Ge(e)??u;return Sn(e,{...P,requests:[...P.requests.filter(G=>!(G.driftKey===L.driftKey&&(G.branch??null)===(L.branch??null))),L]}),{ok:!0,state:L,captured:!!m.capture,created:M}}async function zy(e,t,n){let r={addressed:0,reopened:0},o=ue(),s=$e(e);if(!o||!s)return r;let i=await he("/api/cli/reconcile",{method:"POST",token:o.token,body:JSON.stringify({projectId:t.project.id,branch:s,keys:n})});return i.status!==200?(X(`grum: couldn't update rejected reviews (${typeof i.body.error=="string"?i.body.error:i.status}). The local result still stands.
|
|
558
558
|
`),r):{addressed:Number(i.body.addressed)||0,reopened:Number(i.body.reopened)||0}}async function By(e,t,n,r){let o=ue(),s=$e(e);if(!o||!s)return;let i=ce(e.repoDir,["rev-parse","HEAD"])?.trim()||null,l=await he("/api/cli/reports",{method:"POST",token:o.token,body:JSON.stringify({projectId:t.project.id,branch:s,commit:i,source:r,total:n.length,findings:n.map(a=>({key:a.key,title:a.title,coverage:a.coverage,file:a.places[0]?.file??null,line:a.places[0]?.line??null}))})});l.status!==200&&X(`grum: couldn't report this check (${typeof l.body.error=="string"?l.body.error:l.status}). The local result still stands.
|
|
559
|
-
`)}async function Xc(e,t,n,r){let o=v=>({ok:!1,text:v}),s=vt(r,e.repoDir,mo);if(!s)return o("Send an http or https page URL, or the path to an HTML file in this repo, with --url.");let i=ue();if(!i)return o(`Not signed in to Grum. Run \`${Q} login\`, then ask again.`);let l=$e(e);if(!l)return o("This folder is not on a named git branch. A token is added on a branch, so check one out and ask again.");let a=await nt(e,{interactive:!1});if(!a.ok)return o(a.message);if(a.cached.project.role!=="owner")return o("Only workspace owners can change this.");let c=Zt(e);if(n.category==="button"&&(c.truncated||c.stalled))return o("The local source snapshot is incomplete. Resolve that before adding a button.");let u=Wr(c.files,a.cached,l).findings.find(v=>v.key===t.key);if(!u)return o(`${t.key} isn't new drift in this checkout. Run \`${Q} check\` for the current keys.`);let{key:p,area:d,kind:f,title:m,found:g,foundColor:S,uses:h,places:b}=u;if(n.category!=="button"&&(d==="button"||d==="input"))return o("Adding a token does not approve a component. Use category button to add a button design, or request review for an input change.");let k={projectId:a.cached.project.id,name:n.name,category:n.category,value:n.value,url:s,issue:{key:p,area:d,kind:f,title:m,found:g,foundColor:S,uses:h,places:b},branch:l,observed:Zn(u),...n.category==="button"?{files:[...c.files].filter(([v])=>$l.test(v))}:{}};if(n.category==="button"&&!Kf(k))return o("A button needs its current button drift key, a name of at most 40 characters, and at most 2 MB of source in 1000 files.");let y=await he("/api/cli/contract",{method:"POST",token:i.token,body:JSON.stringify(k)});if(y.status!==200){let v=typeof y.body.error=="string"?y.body.error:`Grum answered ${y.status}.`;return o(v)}let x=ks({schema:1,apiOrigin:V,fetchedAt:new Date().toISOString(),...y.body});return x?n.category==="button"&&(!x.buttonSpecs||!ts(Ba(c.files),x.buttonSpecs).some(v=>v.result==="exact"&&v.spec?.name===n.name))?o("Grum did not return the approved button design. Fetch grum_contract before reporting success."):(Sn(e,x),{ok:!0}):o("Grum did not return a readable contract. The addition could not be verified; fetch grum_contract before reporting success.")}var gr=class{constructor(t=new dt,n=Date.now){this.status=t;this.now=n}status;now;lastAt=null;count=null;slow=!1;begin(){this.status.start("Reading files")}progress=t=>{this.count=t;let n=this.now(),r=this.status.live?Cm:fc;Om(this.lastAt,n,r)&&(this.lastAt=n,this.show())};onSlow=()=>{this.slow=!0,this.show()};checking(t){this.status.start(Pm(t))}done(){this.status.stop()}show(){let t="taking longer than usual";this.status.start(this.count===null?`Reading files is ${t}`:`${Mm(this.count)}${this.slow?` (${t})`:""}`)}};var qy=e=>{try{return wo(e)}catch{return!1}};function zA(){let e=process.versions.node?`v${process.versions.node}`:"";return{runtimeOk:Number(process.versions.node?.split(".")[0])>=li,runtimeVersion:e}}var BA={cursor:[".cursor/hooks.json",".cursor/mcp.json",Fe],codex:[ut,St,qt],claude:[Qe,Ht,kn]},xi=e=>BA[e];function WA(e,t){return t.map(n=>({rel:n,text:re(ze.join(e,n))}))}function Vy(e,t){for(let n of t){let r=ze.join(e,n.rel);n.text===null?GA(r,{force:!0}):ve(r,n.text)}}async function VA(e,t){let n=re(ze.join(e.repoDir,"package.json")),r=[n?Af(n):null,vt("./index.html",e.repoDir,qy)].filter(s=>!!s),o=[];for(let s of r){let i=await t(s);o.push({url:s,verified:i!==null&&i<500})}return o}async function Yc(e){let{request:t}=e,n=null,r=i=>(i.exit==="error"&&n&&n(),i);if(!t.probe&&!t.remove){if(uo(V)){let l=await Vc();if(l==="offline"){let a=await Yt(e);return r(Ae({...a,offline:!0,offlineMessage:"Couldn't reach Grum. The connection is down or too slow (the CLI waits 15 seconds)."},t).result)}if(!ue()){if(l==="expired"){let a=await bi();if(!a.ok){let c=await Yt(e);return r(Ae({...c,offline:!0,offlineMessage:a.message},t).result)}}return r(Ae(await Yt(e),t).result)}}else if(!(await Yt(e)).signedIn){let l=await bi();if(!l.ok){let a=await Yt(e);return r(Ae({...a,offline:!0,offlineMessage:l.message},t).result)}return r(Ae(await Yt(e),t).result)}}let o="";for(let i=0;i<8;i++){let l=await Yt(e),a=Ae(l,t);if(!a.mutation)return r(a.result);let c=JSON.stringify(a.mutation);if(c===o)return r(Ae({...l,offline:!0,offlineMessage:"Setup repeated a step without finishing it."},t).result);o=c;let u=await KA(e,a.mutation,l);if(u.restore&&(n=u.restore),u.result)return r(u.result)}let s=await Yt(e);return r(Ae({...s,offline:!0,offlineMessage:"Setup did not finish after its allowed steps."},t).result)}function HA(e,t){if(!t?.ok)return!1;let n=Ec(t.starter);if(!n)return!1;let r=ze.join(e.repoDir,e.root);return!n.markers.app.every(o=>wo(ze.join(r,o)))}async function qA(e,t,n,r){let o=!!t.projectId&&n.projects?.length===0,i=n.kit!==null&&n.selectedId!==null&&n.scan==="needed"?n.selectedId:o?t.projectId:null;if(!i)return null;let l=i===t.projectId&&r?r:await Jc(i);if(!l.ok)return null;let a=Ec(l.starter);if(!a)return null;let c=ze.join(e.repoDir,e.root),u={exists:d=>wo(ze.join(c,d)),read:d=>{try{return Zc(ze.join(c,d),"utf8")}catch{return null}}},p=vy(e);return{project:l.project,kit:l.kit,needsRepo:o&&l.project.repo===null,starter:a,state:jh(a,u,p),defaultBranch:p.defaultBranch}}function JA(e,t){let n=ze.resolve(e.repoDir,e.root),r=i=>{let l=ze.resolve(n,i);return l.startsWith(n+ze.sep)?l:null},o=r(t.tokensFile),s=Object.keys(t.values.light).length+Object.keys(t.values.dark).length;if(s>0&&(!o||!wo(o)))return{ok:!1,message:`${t.tokensFile} is not in this app, so the contract's tokens were not written.`};try{if(s>0&&o){let i=Zc(o,"utf8"),l=Tc(t,i);l!==i&&Wy(o,l)}for(let i of t.files){let l=r(i.path);if(!l)return{ok:!1,message:`${i.path} is outside this repository.`};let a=wo(l)?Zc(l,"utf8"):null;Oh(i,a)&&(UA(ze.dirname(l),{recursive:!0}),Wy(l,i.content))}}catch(i){return{ok:!1,message:`Couldn't write the starter: ${i instanceof Error?i.message:"unknown error"}.`}}return{ok:!0}}async function Yt(e){let t=ce(e.ws.repoDir,["remote","get-url","origin"])?.trim(),n=t?ai(t):null,r=e.request.projectId??oo(e.ws)??void 0,o=e.request.projectId?await Jc(e.request.projectId):null,s=HA(e.ws,o),i=await Py(e.ws,r,!s),l=e.host(),a=e.request.agent,c=Ct(e.ws),u=uo(V),p=i.projects?.find(S=>S.id===i.selectedId),d=p?ym(e.ws,p.id):!1,f=i.selectedId&&i.scan==="done"?await wi(i.selectedId).catch(()=>null):null,m=null,g=null;if(p&&!p.site&&!d&&n){let S=new gr;S.begin();try{let h=Zt(e.ws,{onSlow:S.onSlow,onProgress:S.progress});m={ms:h.ms,stalled:h.stalled},g=h.files}finally{S.done()}}return{cli:e.cli,serverOrigin:V,repoServer:e.repoServer(),...zA(),origin:t?n?"github":"unsupported":"absent",repoName:n,signedIn:i.signedIn,signInExpired:i.signInExpired,pendingLogin:u,github:i.github,githubMessage:i.githubMessage,projects:i.projects,selectedId:i.selectedId,staleProjectId:i.staleProjectId,scan:i.scan,scanMessage:i.scanMessage,scanProgress:i.scanProgress,localSaved:c,localSkipped:mm(e.ws),localStatus:c?await e.probeLocal(c):void 0,localSuggestions:await VA(e.ws,e.probeLocal),siteSkipped:d,kit:await qA(e.ws,e.request,i,o),siteHints:g&&n?xf(n,g):[],sourceRead:m,adapterWritten:a==="cursor"?l.cursorInstalled:a==="codex"?l.codexInstalled:a==="claude"?l.claudeInstalled:!0,tools:a==="cursor"?l.cursorInstalled?l.cursorTools:"unknown":a==="claude"?l.claudeInstalled?l.claudeTools:"unknown":"not-applicable",hooks:a!=="codex"?"not-applicable":l.codexInstalled?l.codexHooksSeen?"active":"inactive":"unknown",extension:f?{connected:f.connected}:null,extensionUrl:`${V}/extension`,offline:i.offline,offlineMessage:i.offlineMessage}}async function KA(e,t,n){let r=(s,i)=>Ae({...n,...i?{github:i,githubMessage:s}:{offline:!0,offlineMessage:s}},e.request).result;if(t.kind==="switch-server")return{restore:e.switchServer()};if(t.kind==="remove")return await Hy(e.ws.repoDir,t.agent,()=>e.install(t.agent,!0,t.dropPreCommit))?{result:r("Couldn't remove Grum's files, so they were left as they were.")}:{result:rh(n,e.request)};if(t.kind==="select")return n.kit?.state!=="no-app"&&ar(e.ws,t.projectId),{};if(t.kind==="create"){let s=n.repoName;if(!s)return{result:r("This folder has no GitHub repository.")};let i=await Dy(s,{name:t.name,site:t.site,root:e.ws.root||si({repoDir:e.ws.repoDir,cwd:process.cwd()})});return i.ok?(ar(e.ws,i.project.id),{}):{result:i.signedOut?Ae({...n,signedIn:!1,signInExpired:!0},e.request).result:r(i.message,i.github)}}if(t.kind==="link-repo"){let s=await Ny(t.projectId,t.repo);return s.ok?{}:{result:s.signedOut?Ae({...n,signedIn:!1,signInExpired:!0},e.request).result:r(s.message,s.github)}}if(t.kind==="write-starter"){if(!n.kit)return{result:r("There is no starter to write.")};let s=JA(e.ws,n.kit.starter);return s.ok?{}:{result:r(s.message)}}if(t.kind==="start-scan"){let s=await ki(t.projectId);return s.ok?{result:Ae({...n,scan:"running"},e.request).result}:{result:s.signedOut?Ae({...n,signedIn:!1,signInExpired:!0},e.request).result:r(s.message,s.github)}}if(t.kind==="set-site"){let s=await Fy(t.projectId,t.site);return s.ok?(t.site===null&&ac(e.ws,t.projectId),{}):{result:r(s.message)}}if(t.kind==="skip-site")return ac(e.ws,t.projectId),{};if(t.kind==="set-local"){if(t.url===null)return hm(e.ws),{};let s=e.request.localKind==="file"?vt(t.url,e.ws.repoDir,qy):t.url;if(!s)return{result:r("That local app path is not a file in this repository, so it was not saved.")};let i=await e.probeLocal(s);return i===null||i>=500?{result:Ae({...n,localSaved:s,localSkipped:!1,localStatus:i},{...e.request,localProvided:!1}).result}:(lr(e.ws,s),{})}return await Hy(e.ws.repoDir,t.agent,()=>e.install(t.agent,!1,!1))?{result:r("Couldn't write the agent files, so this repository was left as it was.")}:{}}async function Hy(e,t,n){let r=WA(e,[...xi(t),".gitignore"]);try{let o=await n();return o&&Vy(e,r),o}catch(o){throw Vy(e,r),o}}function Jy(e){return re(ze.join(e.stateDir,rr))!==null}function XA(e,t,n){let r=Xn(t.requests,n||null),o=[];for(let s of e)if(s.coverage==="open")o.push({finding:s,revised:!1});else if(s.coverage==="rejected"){let i=r.get(s.key);i?.observed&&i.observed!==Zn(s)&&o.push({finding:s,revised:!0})}return o}var ZA={ask:me,write:X,request:yi};async function Ky(e,t,n,r,o=ZA){let s=$e(e),i=XA(n,t,s).map(c=>({...c,observed:Zn(c.finding)})),l=!0;if(i.length>1){let c=i.map(({finding:p,revised:d},f)=>` ${f+1}. ${p.title}${Qc(p)}${d?" (revised after a rejection)":""}`).join(`
|
|
559
|
+
`)}async function Xc(e,t,n,r){let o=v=>({ok:!1,text:v}),s=vt(r,e.repoDir,mo);if(!s)return o("Send an http or https page URL, or the path to an HTML file in this repo, with --url.");let i=ue();if(!i)return o(`Not signed in to Grum. Run \`${Q} login\`, then ask again.`);let l=$e(e);if(!l)return o("This folder is not on a named git branch. A token is added on a branch, so check one out and ask again.");let a=await nt(e,{interactive:!1});if(!a.ok)return o(a.message);if(a.cached.project.role!=="owner")return o("Only workspace owners can change this.");let c=Zt(e);if(n.category==="button"&&(c.truncated||c.stalled))return o("The local source snapshot is incomplete. Resolve that before adding a button.");let u=Wr(c.files,a.cached,l).findings.find(v=>v.key===t.key);if(!u)return o(`${t.key} isn't new drift in this checkout. Run \`${Q} check\` for the current keys.`);let{key:p,area:d,kind:f,title:m,found:g,foundColor:S,uses:h,places:b}=u;if(n.category!=="button"&&(d==="button"||d==="input"))return o("Adding a token does not approve a component. Use category button to add a button design, or request review for an input change.");let k={projectId:a.cached.project.id,name:n.name,category:n.category,value:n.value,url:s,issue:{key:p,area:d,kind:f,title:m,found:g,foundColor:S,uses:h,places:b},branch:l,observed:Zn(u),...n.category==="button"?{files:[...c.files].filter(([v])=>$l.test(v))}:{}};if(n.category==="button"&&!Kf(k))return o("A button needs its current button drift key, a name of at most 40 characters, and at most 2 MB of source in 1000 files.");let y=await he("/api/cli/contract",{method:"POST",token:i.token,body:JSON.stringify(k)});if(y.status!==200){let v=typeof y.body.error=="string"?y.body.error:`Grum answered ${y.status}.`;return o(v)}let x=ks({schema:1,apiOrigin:V,fetchedAt:new Date().toISOString(),...y.body});return x?n.category==="button"&&(!x.buttonSpecs||!ts(Ba(c.files),x.buttonSpecs).some(v=>v.result==="exact"&&v.spec?.name===n.name))?o("Grum did not return the approved button design. Fetch grum_contract before reporting success."):(Sn(e,x),{ok:!0}):o("Grum did not return a readable contract. The addition could not be verified; fetch grum_contract before reporting success.")}var gr=class{constructor(t=new dt,n=Date.now){this.status=t;this.now=n}status;now;lastAt=null;count=null;slow=!1;begin(){this.status.start("Reading files")}progress=t=>{this.count=t;let n=this.now(),r=this.status.live?Cm:fc;Om(this.lastAt,n,r)&&(this.lastAt=n,this.show())};onSlow=()=>{this.slow=!0,this.show()};checking(t){this.status.start(Pm(t))}done(){this.status.stop()}show(){let t="taking longer than usual";this.status.start(this.count===null?`Reading files is ${t}`:`${Mm(this.count)}${this.slow?` (${t})`:""}`)}};var qy=e=>{try{return wo(e)}catch{return!1}};function zA(){let e=process.versions.node?`v${process.versions.node}`:"";return{runtimeOk:Number(process.versions.node?.split(".")[0])>=li,runtimeVersion:e}}var BA={cursor:[".cursor/hooks.json",".cursor/mcp.json",Fe],codex:[ut,St,qt],claude:[Qe,Ht,kn]},xi=e=>BA[e];function WA(e,t){return t.map(n=>({rel:n,text:re(ze.join(e,n))}))}function Vy(e,t){for(let n of t){let r=ze.join(e,n.rel);n.text===null?GA(r,{force:!0}):ve(r,n.text)}}async function VA(e,t){let n=re(ze.join(e.repoDir,"package.json")),r=[n?Af(n):null,vt("./index.html",e.repoDir,qy)].filter(s=>!!s),o=[];for(let s of r){let i=await t(s);o.push({url:s,verified:i!==null&&i<500})}return o}async function Yc(e){let{request:t}=e,n=null,r=i=>(i.exit==="error"&&n&&n(),i);if(!t.probe&&!t.remove){if(uo(V)){let l=await Vc();if(l==="offline"){let a=await Yt(e);return r(Ae({...a,offline:!0,offlineMessage:"Couldn't reach Grum. The connection is down or too slow (the CLI waits 15 seconds)."},t).result)}if(!ue()){if(l==="expired"){let a=await bi();if(!a.ok){let c=await Yt(e);return r(Ae({...c,offline:!0,offlineMessage:a.message},t).result)}}return r(Ae(await Yt(e),t).result)}}else if(!(await Yt(e)).signedIn){let l=await bi();if(!l.ok){let a=await Yt(e);return r(Ae({...a,offline:!0,offlineMessage:l.message},t).result)}return r(Ae(await Yt(e),t).result)}}let o="";for(let i=0;i<8;i++){let l=await Yt(e),a=Ae(l,t);if(!a.mutation)return r(a.result);let c=JSON.stringify(a.mutation);if(c===o)return r(Ae({...l,offline:!0,offlineMessage:"Setup repeated a step without finishing it."},t).result);o=c;let u=await KA(e,a.mutation,l);if(u.restore&&(n=u.restore),u.result)return r(u.result)}let s=await Yt(e);return r(Ae({...s,offline:!0,offlineMessage:"Setup did not finish after its allowed steps."},t).result)}function HA(e,t){if(!t?.ok)return!1;let n=Ec(t.starter);if(!n)return!1;let r=ze.join(e.repoDir,e.root);return!n.markers.app.every(o=>wo(ze.join(r,o)))}async function qA(e,t,n,r){let o=!!t.projectId&&n.projects?.length===0,i=n.kit!==null&&n.selectedId!==null&&n.scan==="needed"?n.selectedId:o?t.projectId:null;if(!i)return null;let l=i===t.projectId&&r?r:await Jc(i);if(!l.ok)return null;let a=Ec(l.starter);if(!a)return null;let c=ze.join(e.repoDir,e.root),u={exists:d=>wo(ze.join(c,d)),read:d=>{try{return Zc(ze.join(c,d),"utf8")}catch{return null}}},p=vy(e);return{project:l.project,kit:l.kit,needsRepo:o&&l.project.repo===null,starter:a,state:jh(a,u,p),defaultBranch:p.defaultBranch}}function JA(e,t){let n=ze.resolve(e.repoDir,e.root),r=i=>{let l=ze.resolve(n,i);return l.startsWith(n+ze.sep)?l:null},o=r(t.tokensFile),s=Object.keys(t.values.light).length+Object.keys(t.values.dark).length;if(s>0&&(!o||!wo(o)))return{ok:!1,message:`${t.tokensFile} is not in this app, so the contract's tokens were not written.`};try{if(s>0&&o){let i=Zc(o,"utf8"),l=Tc(t,i);l!==i&&Wy(o,l)}for(let i of t.files){let l=r(i.path);if(!l)return{ok:!1,message:`${i.path} is outside this repository.`};let a=wo(l)?Zc(l,"utf8"):null;Oh(i,a)&&(UA(ze.dirname(l),{recursive:!0}),Wy(l,i.content))}}catch(i){return{ok:!1,message:`Couldn't write the starter: ${i instanceof Error?i.message:"unknown error"}.`}}return{ok:!0}}async function Yt(e){let t=ce(e.ws.repoDir,["remote","get-url","origin"])?.trim(),n=t?ai(t):null,r=e.request.projectId??oo(e.ws)??void 0,o=e.request.projectId?await Jc(e.request.projectId):null,s=HA(e.ws,o),i=await Py(e.ws,r,!s),l=e.host(),a=e.request.agent,c=Ct(e.ws),u=uo(V),p=i.projects?.find(S=>S.id===i.selectedId),d=p?ym(e.ws,p.id):!1,f=i.selectedId&&i.scan==="done"?await wi(i.selectedId).catch(()=>null):null,m=null,g=null;if(p&&!p.site&&!d&&n){let S=new gr;S.begin();try{let h=Zt(e.ws,{onSlow:S.onSlow,onProgress:S.progress});m={ms:h.ms,stalled:h.stalled},g=h.files}finally{S.done()}}return{cli:e.cli,serverOrigin:V,repoServer:e.repoServer(),...zA(),origin:t?n?"github":"unsupported":"absent",repoName:n,signedIn:i.signedIn,signInExpired:i.signInExpired,pendingLogin:u,github:i.github,githubMessage:i.githubMessage,projects:i.projects,selectedId:i.selectedId,staleProjectId:i.staleProjectId,scan:i.scan,scanMessage:i.scanMessage,scanProgress:i.scanProgress,localSaved:c,localSkipped:mm(e.ws),localStatus:c?await e.probeLocal(c):void 0,localSuggestions:await VA(e.ws,e.probeLocal),siteSkipped:d,kit:await qA(e.ws,e.request,i,o),siteHints:g&&n?xf(n,g):[],sourceRead:m,adapterWritten:a==="cursor"?l.cursorInstalled:a==="codex"?l.codexInstalled:a==="claude"?l.claudeInstalled:!0,tools:a==="cursor"?l.cursorInstalled?l.cursorTools:"unknown":a==="claude"?l.claudeInstalled?l.claudeTools:"unknown":"not-applicable",hooks:a!=="codex"?"not-applicable":l.codexInstalled&&l.codexHooksSeen?"active":"unknown",extension:f?{connected:f.connected}:null,extensionUrl:`${V}/extension`,offline:i.offline,offlineMessage:i.offlineMessage}}async function KA(e,t,n){let r=(s,i)=>Ae({...n,...i?{github:i,githubMessage:s}:{offline:!0,offlineMessage:s}},e.request).result;if(t.kind==="switch-server")return{restore:e.switchServer()};if(t.kind==="remove")return await Hy(e.ws.repoDir,t.agent,()=>e.install(t.agent,!0,t.dropPreCommit))?{result:r("Couldn't remove Grum's files, so they were left as they were.")}:{result:rh(n,e.request)};if(t.kind==="select")return n.kit?.state!=="no-app"&&ar(e.ws,t.projectId),{};if(t.kind==="create"){let s=n.repoName;if(!s)return{result:r("This folder has no GitHub repository.")};let i=await Dy(s,{name:t.name,site:t.site,root:e.ws.root||si({repoDir:e.ws.repoDir,cwd:process.cwd()})});return i.ok?(ar(e.ws,i.project.id),{}):{result:i.signedOut?Ae({...n,signedIn:!1,signInExpired:!0},e.request).result:r(i.message,i.github)}}if(t.kind==="link-repo"){let s=await Ny(t.projectId,t.repo);return s.ok?{}:{result:s.signedOut?Ae({...n,signedIn:!1,signInExpired:!0},e.request).result:r(s.message,s.github)}}if(t.kind==="write-starter"){if(!n.kit)return{result:r("There is no starter to write.")};let s=JA(e.ws,n.kit.starter);return s.ok?{}:{result:r(s.message)}}if(t.kind==="start-scan"){let s=await ki(t.projectId);return s.ok?{result:Ae({...n,scan:"running"},e.request).result}:{result:s.signedOut?Ae({...n,signedIn:!1,signInExpired:!0},e.request).result:r(s.message,s.github)}}if(t.kind==="set-site"){let s=await Fy(t.projectId,t.site);return s.ok?(t.site===null&&ac(e.ws,t.projectId),{}):{result:r(s.message)}}if(t.kind==="skip-site")return ac(e.ws,t.projectId),{};if(t.kind==="set-local"){if(t.url===null)return hm(e.ws),{};let s=e.request.localKind==="file"?vt(t.url,e.ws.repoDir,qy):t.url;if(!s)return{result:r("That local app path is not a file in this repository, so it was not saved.")};let i=await e.probeLocal(s);return i===null||i>=500?{result:Ae({...n,localSaved:s,localSkipped:!1,localStatus:i},{...e.request,localProvided:!1}).result}:(lr(e.ws,s),{})}return await Hy(e.ws.repoDir,t.agent,()=>e.install(t.agent,!1,!1))?{result:r("Couldn't write the agent files, so this repository was left as it was.")}:{}}async function Hy(e,t,n){let r=WA(e,[...xi(t),".gitignore"]);try{let o=await n();return o&&Vy(e,r),o}catch(o){throw Vy(e,r),o}}function Jy(e){return re(ze.join(e.stateDir,rr))!==null}function XA(e,t,n){let r=Xn(t.requests,n||null),o=[];for(let s of e)if(s.coverage==="open")o.push({finding:s,revised:!1});else if(s.coverage==="rejected"){let i=r.get(s.key);i?.observed&&i.observed!==Zn(s)&&o.push({finding:s,revised:!0})}return o}var ZA={ask:me,write:X,request:yi};async function Ky(e,t,n,r,o=ZA){let s=$e(e),i=XA(n,t,s).map(c=>({...c,observed:Zn(c.finding)})),l=!0;if(i.length>1){let c=i.map(({finding:p,revised:d},f)=>` ${f+1}. ${p.title}${Qc(p)}${d?" (revised after a rejection)":""}`).join(`
|
|
560
560
|
`),u=await o.ask(`
|
|
561
561
|
${i.length} items aren't in the contract:
|
|
562
562
|
${c}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grum-os/cli",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"contentHash": "
|
|
3
|
+
"version": "0.3.38",
|
|
4
|
+
"contentHash": "3106fdfb601b20e9df95c8e127d60964",
|
|
5
5
|
"description": "Keep a repository on the design contract reviewed in Grum: a local drift check, review requests, Cursor hooks and an MCP server.",
|
|
6
6
|
"homepage": "https://www.grum.so",
|
|
7
7
|
"keywords": [
|