@grum-os/cli 0.3.3 → 0.3.4

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 ADDED
@@ -0,0 +1,224 @@
1
+ # grum
2
+
3
+ Keep a repository on the design contract your team reviewed in [Grum](https://app.grum.so).
4
+
5
+ Grum reads the tokens the repository declares (CSS custom properties, a Tailwind v4 `@theme`, SCSS variables) and the button looks it defines. The team can add tokens in Grum before the code has them, and move others outside the contract. That reviewed set is what `grum` checks against. It does not invent a contract of its own, and it does not publish one.
6
+
7
+ On your machine it compares the working tree to that contract, lists only drift that is new on this branch, and can send a new item to the project's owners for review, with a picture of how it renders. The same check runs inside Cursor and, if you want, before a commit.
8
+
9
+ A check on its own prints a report and exits 0, including when you are signed out, offline, or the repository has never been scanned. It fails the process only when you ask it to.
10
+
11
+ Requires Node.js 22 or newer.
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ npm install -g @grum-os/cli
17
+ ```
18
+
19
+ Or run it without installing:
20
+
21
+ ```sh
22
+ npx @grum-os/cli
23
+ ```
24
+
25
+ `grum` with no command opens a menu: check this repository, set it up, or sign in and out.
26
+
27
+ ## Set up a repository
28
+
29
+ From the repository root:
30
+
31
+ ```sh
32
+ grum setup
33
+ ```
34
+
35
+ Setup does three things.
36
+
37
+ 1. **Signs this computer in.** It prints a code and a link. Approve the code in the browser. The token is stored in your home directory (`~/.config/grum/credentials/`), never in the repository. `grum logout` removes it. Grum's Settings page lists every computer that is signed in, and can sign one out.
38
+ 2. **Links the git remote to its Grum project.** If several workspaces use this repository, it asks which one. If none does yet, it offers to create a workspace, asks for the live site, and waits while Grum runs the first scan.
39
+ 3. **Asks whether to set up Cursor.** That writes the hooks, the MCP server, the rule pointer, and a pre-commit check described below. Enter accepts. `n` skips it.
40
+
41
+ A new workspace needs GitHub connected in Grum, so the scan can read the repository.
42
+
43
+ ## Check for drift
44
+
45
+ ```sh
46
+ grum check
47
+ ```
48
+
49
+ New drift is grouped by file. Each line names the value, the kind of drift, whether anyone has asked for review, and a one-line fix:
50
+
51
+ ```text
52
+ src/pages/Hero.tsx
53
+ 18: Hard-coded #3C3C3C instead of --text-primary [Hard-coded value, Not sent for review]
54
+ fix: Use --text-primary (#1B1B1B) instead of #3C3C3C.
55
+
56
+ 1 new drift item against Acme's contract: 1 not sent for review (42 files checked).
57
+ ```
58
+
59
+ Drift the latest scan already has on the default branch is counted, not listed. So is drift someone dismissed on the Drift page. A branch that only repeats those looks clean:
60
+
61
+ ```text
62
+ No new drift against Acme's contract (42 files checked). 3 known issues are on Acme's Drift page.
63
+ ```
64
+
65
+ Using a known value in more places than the default branch does is new drift.
66
+
67
+ In a terminal, the check then offers each new item for review, one at a time. You can add a reason. Grum captures how the element renders and attaches the picture to the request. Owners approve or reject it on the Review page. Allowing it also dismisses that drift. A rejected item is not offered again: change the code to use the contract. If the code changed after the rejection, the next check offers it once more as a revised proposal, and the earlier note stays on the request.
68
+
69
+ A request covers the branch it was asked on. It covers every branch once the change reaches the default branch and a scan counts the drift as existing. A full check also marks a rejected review **Addressed** when the drift is gone, and reopens it when the drift comes back.
70
+
71
+ ### Flags
72
+
73
+ ```sh
74
+ grum check --staged # drift in the blocks staged for commit
75
+ grum check --files src/Hero.tsx # only these paths
76
+ grum check --json # project, cache time, known count, findings
77
+ grum check --no-review # print the report and do not ask
78
+ grum check --cursor # open the findings in Cursor
79
+ grum check --offline # the cached contract, no network
80
+ grum check --root apps/web # the app lives in a subdirectory
81
+ grum check --report # also post this run to Grum
82
+ grum check --ci # fail on open or rejected drift, and post the run
83
+ grum check --strict # like --ci, and also fail while a review is pending
84
+ ```
85
+
86
+ `--json` is what you want when another program needs the drift key. CI, JSON, staged, and agent runs never prompt and never open Cursor.
87
+
88
+ `--ci` and `--report` show up on the Drift page under Recent checks, one latest run per branch.
89
+
90
+ ### What it looks for
91
+
92
+ | Area | New drift |
93
+ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
94
+ | Color | A hard-coded color, a color nearly identical to a contract color, or a color token the contract does not have |
95
+ | Typography | A size, weight, or line height off the contract scale |
96
+ | Spacing | A margin, padding, or gap off the scale |
97
+ | Radius | A radius off the scale |
98
+ | Buttons | A look the contract does not have, a look whose state changed, a missing state, a raw `<button>` outside the button component, or a variant the component lacks |
99
+
100
+ ## Ask for review of one item
101
+
102
+ The key is the stable id of a finding (`color:raw:#3C3C3C` and the like). `grum check --json` prints it on each finding.
103
+
104
+ ```sh
105
+ grum request "color:raw:#3C3C3C" \
106
+ --reason "The marketing hero uses this gray" \
107
+ --url http://localhost:3000
108
+ ```
109
+
110
+ `--url` is the page where the element renders. It can also be an HTML file in the repo (`./index.html`) when there is no dev server. A page behind a login is captured through the Grum Chrome extension, in your own browser session. `--browser chrome` or `--browser safari` picks the browser. `--no-capture` sends the request with no picture.
111
+
112
+ ```sh
113
+ grum capture reset
114
+ ```
115
+
116
+ Clears the capture-browser sign-in saved for this repository.
117
+
118
+ ## Read the contract
119
+
120
+ ```sh
121
+ grum contract
122
+ ```
123
+
124
+ Prints the packet an agent reads before writing styles: tokens by category (with a separate dark value when there is one), button components, each look and the prop that selects it, and the repository's written rules about those buttons. Review decisions are included, so a pending or rejected item is visible without another check.
125
+
126
+ ## Cursor
127
+
128
+ ```sh
129
+ grum init cursor
130
+ ```
131
+
132
+ Merges into files that already exist. Entries Grum did not write are left as they are, and a second run changes nothing. It writes:
133
+
134
+ | File | What it does |
135
+ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
136
+ | `.cursor/hooks.json` | After an edit, checks the block that changed. The stop hook sends the agent back while blocking drift stands, at most twice a conversation. The shell hook can refuse `git commit` and `git push`. Hooks read the cache and do not call the network. |
137
+ | `.cursor/mcp.json` | Four tools: `grum_contract`, `grum_check`, `grum_request_review`, `grum_review_status`. |
138
+ | `.cursor/rules/grum.mdc` | Tells the agent to read the contract before writing UI and to check before it stops. The contract itself stays in Grum, so this file does not go stale on every scan. |
139
+ | pre-commit | Adds `grum check --staged`. Husky's hook is committed with the repo. Git's own pre-commit hook is local and is not shared. A lefthook repo gets the snippet to paste. |
140
+
141
+ `.grum/` is added to `.gitignore` the first time Grum writes there. The cached contract (`.grum/contract.json`) stays on this computer.
142
+
143
+ Reload the Cursor window after init (or turn **grum** on under Settings, Tools and MCP) so the agent sees the tools. Until then it follows `.grum/contract.json`.
144
+
145
+ ```sh
146
+ grum init cursor --remove # deletes exactly what init added
147
+ grum init cursor --force # replaces a rule file Grum did not write
148
+ grum init cursor --local-only # hooks and the rule, without a linked project
149
+ ```
150
+
151
+ ## Fail the check
152
+
153
+ Reporting is the default, so a missing sign-in cannot break a commit. To fail when drift was never sent for review, or was rejected:
154
+
155
+ ```sh
156
+ grum check --ci
157
+ ```
158
+
159
+ `--strict` also fails while a review is still pending. A pending review passes `--ci`.
160
+
161
+ The same rule can live in `.grum/config.json`:
162
+
163
+ ```json
164
+ {
165
+ "root": "apps/web",
166
+ "block": true,
167
+ "denyGit": ["commit", "push"],
168
+ "maxFollowups": 2
169
+ }
170
+ ```
171
+
172
+ | Field | Meaning |
173
+ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
174
+ | `root` | Directory to scan when the app is not at the repository root. `grum check --root` does the same for one run. |
175
+ | `block` | `true` makes `grum check` fail on open or rejected drift, and makes Cursor refuse the git commands below while that drift is in what they would send. |
176
+ | `denyGit` | `commit`, `push`, or both. With `block` and no `denyGit`, both are refused. |
177
+ | `maxFollowups` | How many times the Cursor stop hook sends the agent back in one conversation. Default is 2. |
178
+
179
+ `block` does not fail a check that is still waiting on review. Use `--strict` for that.
180
+
181
+ Grum adds `.grum/` to `.gitignore` the first time it writes there, so `config.json` stays on this computer. To commit just that file, ignore the directory's contents and keep it: `.grum/*`, then `!.grum/config.json`.
182
+
183
+ ## Continuous integration
184
+
185
+ Sign in once with `grum login`, then copy the `token` field from the credentials file for this Grum. For [app.grum.so](https://app.grum.so) that file is:
186
+
187
+ ```text
188
+ ~/.config/grum/credentials/https_app_grum_so.json
189
+ ```
190
+
191
+ Store it as a secret. Do not commit it. The token acts as you, in every workspace you belong to. Settings in Grum lists these sign-ins and can revoke one.
192
+
193
+ ```yaml
194
+ # GitHub Actions
195
+ - name: Check the design contract
196
+ run: npx --yes @grum-os/cli check --ci
197
+ env:
198
+ GRUM_TOKEN: ${{ secrets.GRUM_TOKEN }}
199
+ ```
200
+
201
+ `GRUM_TOKEN` is used instead of the token saved on the machine.
202
+
203
+ ## Commands
204
+
205
+ | Command | What it does |
206
+ | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
207
+ | `grum` | Menu: check, setup, or sign in and out. |
208
+ | `grum login` / `grum logout` | Sign this computer in to Grum, or out. |
209
+ | `grum setup [--root <dir>]` | Sign in, link this repository, and ask about Cursor. |
210
+ | `grum check [flags]` | Check the working tree. See [Flags](#flags). |
211
+ | `grum request <key> [--reason "..."] [--url <page>] [--browser chrome\|safari] [--no-capture]` | Ask the owners to allow one drift item. |
212
+ | `grum capture reset` | Clear this repository's saved capture-browser sign-ins. |
213
+ | `grum contract` | Print the contract packet. |
214
+ | `grum init cursor [--remove] [--force] [--local-only]` | Install or remove the Cursor and pre-commit setup. |
215
+
216
+ `grum hook` and `grum mcp` are the entry points Cursor runs. You do not call them yourself.
217
+
218
+ ## Environment
219
+
220
+ | Variable | What it does |
221
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------ |
222
+ | `GRUM_TOKEN` | Sign in for CI. Overrides the token saved on this computer. |
223
+ | `GRUM_API_ORIGIN` | Talk to a Grum other than `https://app.grum.so`. An `http` or `https` origin, with no user or password in the URL. |
224
+ | `GRUM_NO_BROWSER` | Do not open a browser during sign-in. The code and link are still printed. |
package/content.md5 CHANGED
@@ -1 +1 @@
1
- 35ed61e10b9e301a5309e2fb84e8601d
1
+ 915bb0fcab45a45db629397f8efd6a7e
package/grum.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- globalThis.__GRUM_VERSION__="0.3.3";
2
+ globalThis.__GRUM_VERSION__="0.3.4";
3
3
  import{chmodSync as Ng,existsSync as It,rmSync as cs}from"node:fs";import B from"node:path";import{createInterface as Fg,emitKeypressEvents as Ug}from"node:readline";import{fileURLToPath as Bg}from"node:url";var ys=(e,t,n)=>Math.min(n,Math.max(t,e)),Pt=e=>ys(Math.round(e),0,255).toString(16).padStart(2,"0").toUpperCase();function Ll({r:e,g:t,b:n,a:r}){let s=`#${Pt(e)}${Pt(t)}${Pt(n)}`,o=Math.round(ys(r,0,1)*255);return o>=255?s:`${s}${Pt(o)}`}function Ml(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(o=>o+o).join(""):t,r=parseInt(n.slice(0,6),16),s=n.length===8?parseInt(n.slice(6,8),16)/255:1;return{r:r>>16&255,g:r>>8&255,b:r&255,a:s}}function Lt(e,t){let n=e.trim();return n.endsWith("%")?parseFloat(n)/100*t:parseFloat(n)}function Dl(e,t,n){let r=o=>(o+e/30)%12,s=o=>n-t*Math.min(n,1-n)*Math.max(-1,Math.min(r(o)-3,9-r(o),1));return[s(0)*255,s(8)*255,s(4)*255]}function Dn(e){let t=e.trim().toLowerCase();if(t.startsWith("#"))return Ml(t);let n=t.match(/^(rgba?|hsla?)\(([^)]*)\)$/);if(!n?.[1]||n[2]==null)return null;let r=n[2].split(/[\s,/]+/).filter(Boolean);if(r.length<3)return null;let s=r[3]!=null?Lt(r[3],1):1;if(n[1].startsWith("rgb")){let[p,f,y]=r.slice(0,3).map(b=>Lt(b,255));return[p,f,y,s].some(Number.isNaN)?null:{r:p,g:f,b:y,a:s}}let o=parseFloat(r[0]),i=Lt(r[1],1),l=Lt(r[2],1);if([o,i,l,s].some(Number.isNaN))return null;let[a,c,u]=Dl((o%360+360)%360,i,l);return{r:a,g:c,b:u,a:s}}function pe(e){let t=Dn(e);return t?Ll(t):null}function hs({r:e,g:t,b:n}){let r=p=>{let f=p/255;return f<=.04045?f/12.92:((f+.055)/1.055)**2.4},[s,o,i]=[r(e),r(t),r(n)],l=(s*.4124+o*.3576+i*.1805)/.95047,a=s*.2126+o*.7152+i*.0722,c=(s*.0193+o*.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 On(e){return`--color-${e.replace(/^#/,"").slice(0,6).toLowerCase()}`}function Mt(e,t){let n=Dn(e),r=Dn(t);if(!n||!r)return 1/0;let[s,o,i]=hs(n),[l,a,c]=hs(r);return Math.hypot(s-l,o-a,i-c)}var Nn=/#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3,4})\b|\b(?:rgba?|hsla?)\([^)]*\)/gi;var Fn={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 ws(e){let t=e.lastIndexOf("-");return t<=0?null:Fn[e.slice(0,t)]?.[e.slice(t+1)]??null}function bs(e){let t=[],n=0,r=0;for(let s=0;s<e.length;s++){let o=e[s];o==="("||o==="["?n++:o===")"||o==="]"?n=Math.max(0,n-1):o===","&&n===0&&(t.push(e.slice(r,s).trim()),r=s+1)}return t.push(e.slice(r).trim()),t.filter(Boolean)}function Ol(e,t){if(!e)return t;let n=bs(e);return bs(t).flatMap(r=>n.map(s=>r.includes("&")?r.replaceAll("&",s):`${s} ${r}`)).join(", ")}var Nl=/^@(?:-\w+-)?(?:keyframes|font-face|property)\b/i;function Un(e){let t=e.replace(/\/\*[\s\S]*?\*\//g,i=>i.replace(/[^\n]/g," ")),n=[],r=[],s=0,o=1;for(let i=0;i<t.length;i++){let l=t[i];if(l===`
4
4
  `&&o++,l==="{"){let a=t.slice(s,i).trim(),c=r[r.length-1];c&&(c.own+=t.slice(c.segStart,s));let u=/^@theme\b/i.test(a),p=a.startsWith("@")&&!u,f=[...r].reverse().find(y=>!y.isAt)??null;r.push({raw:a,resolved:p?a:u?":root":Ol(f?.resolved??null,a),isAt:p,skip:(c?.skip??!1)||p&&Nl.test(a),line:o,own:"",segStart:i+1}),s=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)}s=i+1}else l===";"&&(s=i+1)}return n}function Bn(e){let t=[],n=0,r=0,s=o=>{let i=o.indexOf(":");if(i<=0)return;let l=o.slice(0,i).trim(),a=o.slice(i+1).replace(/\s+/g," ").trim();/^-{0,2}[a-z][\w-]*$/i.test(l)&&a&&t.push({property:l,value:a})};for(let o=0;o<e.length;o++){let i=e[o];i==="("?n++:i===")"?n=Math.max(0,n-1):i===";"&&n===0&&(s(e.slice(r,o)),r=o+1)}return s(e.slice(r)),t}var ks={color:"Color",typography:"Typography",spacing:"Spacing",radius:"Radius",shadow:"Shadows",other:"Other"},Fl=/\.(css|scss|sass|less|pcss)$/i,Ul=/^(?::root|html|body|:host)?(?:\[data-[\w-]+(?:[~|^$*]?=[^\]]*)?\]|\.(?:light|dark|theme-[\w-]+))*$/i;function xs(e){let t=e.split(",").map(n=>n.trim());return t.length>0&&t.every(n=>n!==""&&Ul.test(n))}function vs(e){return Fl.test(e)}var Vn=["color","typography","spacing","radius","button"],Hn={color:"Color",typography:"Typography",spacing:"Spacing",radius:"Radius",button:"Buttons"},Ot={raw:"Hard-coded value",near:"Near-identical colors",undefined:"Token not in contract","look-new":"Button not in contract","look-changed":"Button look changed","state-missing":"Button state missing","native-button":"Raw <button>","prop-unknown":"Unknown button variant"},Bl=/\.(tsx|jsx|ts|js|mjs|vue|svelte|astro|html)$/i,Kn=/(^|\/)(tests?|__tests__|__mocks__|fixtures|stories)\/|\.(test|spec|stories)\.|(^|\/)(tailwind|postcss|vite|next|webpack|eslint)\.config\.|\.d\.ts$/i,zl=[[/^(color|background(-color)?|border(-(top|right|bottom|left))?(-color)?|outline(-color)?|fill|stroke|caret-color|accent-color|text-decoration-color|box-shadow)$/i,"color"],[/^(font-size|line-height|letter-spacing|font)$/i,"typography"],[/^(padding|margin)(-(top|right|bottom|left|inline|block)(-(start|end))?)?$|^(gap|row-gap|column-gap)$/i,"spacing"],[/^border(-(top|bottom)-(left|right))?-radius$/i,"radius"]];function Gl(e){return zl.find(([t])=>t.test(e))?.[1]??null}var ql=/(?<![\w.#-])(\d*\.?\d+)(px|rem)\b/g,$s=/var\(\s*(--[\w-]+)/g,Vl=[[/\b(?:p|px|py|pt|pr|pb|pl|ps|pe|m|mx|my|mt|mr|mb|ml|gap|gap-x|gap-y|space-x|space-y)-\[(\d*\.?\d+(?:px|rem))\]/g,"spacing","spacing"],[/\btext-\[(\d*\.?\d+(?:px|rem))\]/g,"typography","font-size"],[/\bleading-\[(\d*\.?\d+(?:px|rem))\]/g,"typography","line-height"],[/\brounded(?:-[trblse]{1,2})?-\[(\d*\.?\d+(?:px|rem))\]/g,"radius","border-radius"]],Wl=[[/(?<![\w[-])(?:p|px|py|pt|pr|pb|pl|m|mx|my|mt|mr|mb|ml|gap|gap-x|gap-y|space-x|space-y)-(?:\d+(?:\.5)?|px)(?![\w[])/g,"spacing"],[/(?<![\w[-])text-(?:xs|sm|base|lg|[2-9]?xl)(?![\w[-])/g,"typography"],[/(?<![\w[-])rounded(?:-[trblse]{1,2})?(?:-(?:none|sm|md|lg|[2-3]?xl|full))?(?![\w[-])/g,"radius"]],Hl=/(?<![\w-])(bg|text|border|ring|fill|stroke|outline|from|via|to|divide|placeholder|decoration)-([a-z][\w-]*)/g,Kl={bg:"background",from:"background",via:"background",to:"background",border:"border-color",ring:"border-color",outline:"border-color",divide:"border-color"};function Jl(e){if(e==="white")return"#FFFFFF";if(e==="black")return"#000000";let t=e.match(/^([a-z]+)-(\d{2,3})$/);return t?Fn[t[1]]?.[t[2]]?.toUpperCase()??null:null}function Nt(e){let t=e.trim();return t.length>140?`${t.slice(0,137)}\u2026`:t}function Xl(e,t){return!e.startsWith("#")||e.length>5?!0:/[a-f]/i.test(e)||/[:'"`[(]\s*$/.test(t)}function Zl(e,t,n,r,s){let o=t.split(`
5
5
  `);for(let i of Un(t)){let l=xs(i.selector);for(let a of Bn(i.body)){if(a.property.startsWith("--")){!l&&!s.has(a.property)&&s.set(a.property,a.value);continue}if(a.property.startsWith("$"))continue;let c=Gl(a.property);if(!c)continue;let u=o.findIndex((b,h)=>h>=i.line-1&&b.includes(a.property)),p=u>=0?u+1:i.line,f={file:e,line:p,snippet:Nt(o[p-1]??`${a.property}: ${a.value}`)};for(let b of a.value.matchAll($s))r.push({area:c,name:b[1],property:a.property,place:f});let y=a.value.replace(/var\([^)]*\)/g,"");if(c==="color")for(let b of y.matchAll(Nn))n.push({area:c,value:b[0],property:a.property,place:f});else for(let b of y.matchAll(ql))parseFloat(b[1])!==0&&n.push({area:c,value:`${b[1]}${b[2]}`,property:a.property,place:f})}}}function Yl(e,t,n,r,s,o){t.split(`
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@grum-os/cli",
3
- "version": "0.3.3",
4
- "contentHash": "35ed61e10b9e301a5309e2fb84e8601d",
5
- "description": "Keep code on your design contract: a local drift check, review requests, Cursor hooks and an MCP server.",
3
+ "version": "0.3.4",
4
+ "contentHash": "915bb0fcab45a45db629397f8efd6a7e",
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
+ "homepage": "https://app.grum.so",
6
7
  "keywords": [
7
8
  "design",
8
9
  "design-system",
@@ -19,7 +20,8 @@
19
20
  },
20
21
  "files": [
21
22
  "grum.mjs",
22
- "content.md5"
23
+ "content.md5",
24
+ "README.md"
23
25
  ],
24
26
  "engines": {
25
27
  "node": ">=22"