sitelines 1.0.0

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.
Files changed (36) hide show
  1. package/DESIGN.md +129 -0
  2. package/LICENSE +21 -0
  3. package/README.md +336 -0
  4. package/bin/sitelines.mjs +155 -0
  5. package/examples/demo-site/about/index.html +30 -0
  6. package/examples/demo-site/assets/auth.js +3 -0
  7. package/examples/demo-site/assets/site.css +27 -0
  8. package/examples/demo-site/blog/index.html +31 -0
  9. package/examples/demo-site/blog/introducing-meridian/index.html +30 -0
  10. package/examples/demo-site/changelog/index.html +30 -0
  11. package/examples/demo-site/contact/index.html +33 -0
  12. package/examples/demo-site/docs/api/index.html +31 -0
  13. package/examples/demo-site/docs/api/webhooks/index.html +30 -0
  14. package/examples/demo-site/docs/api/webhooks/retries/index.html +30 -0
  15. package/examples/demo-site/docs/index.html +32 -0
  16. package/examples/demo-site/docs/quickstart/index.html +31 -0
  17. package/examples/demo-site/index.html +37 -0
  18. package/examples/demo-site/legal/privacy/index.html +29 -0
  19. package/examples/demo-site/legal/terms/index.html +29 -0
  20. package/examples/demo-site/login/index.html +35 -0
  21. package/examples/demo-site/pricing/index.html +33 -0
  22. package/examples/demo-site/product/index.html +31 -0
  23. package/examples/demo-site/product/integrations/index.html +31 -0
  24. package/examples/demo-site/signup/index.html +35 -0
  25. package/examples/demo-site/status/index.html +15 -0
  26. package/examples/demo-site/thanks/index.html +21 -0
  27. package/package.json +53 -0
  28. package/scripts/install-skill.mjs +145 -0
  29. package/scripts/scan.mjs +399 -0
  30. package/scripts/serve.mjs +207 -0
  31. package/skill/AGENTS.md +61 -0
  32. package/skill/SKILL.md +170 -0
  33. package/skill/references/edit-protocol.md +57 -0
  34. package/viewer/app.js +1373 -0
  35. package/viewer/index.html +202 -0
  36. package/viewer/style.css +616 -0
package/DESIGN.md ADDED
@@ -0,0 +1,129 @@
1
+ # sitelines viewer - design
2
+
3
+ The authority for `viewer/style.css`. Read this before changing anything visual.
4
+
5
+ **Mode: Operate.** The visitor is a developer holding a question: *where does this button go, and what did I break?*
6
+ Scanability, state legibility, and keyboard reach outrank expression. The brand lives in precision, not in
7
+ personality. If a decision makes the map prettier and the answer slower, the decision is wrong.
8
+
9
+ ## World
10
+
11
+ A **plan view**. The map is a technical drawing of a site: pages are cards laid out on a measured ground,
12
+ depth columns are ruled like a drafting grid, links are drawn wires with a legible arrowhead. Nothing is
13
+ themed, gamified, or narrated. The visual interest comes from real density and real hierarchy: forty live
14
+ page previews arranged by how many clicks they sit from the front door.
15
+
16
+ The tool runs beside an editor, all day, in whatever ambient light the developer already chose. So it ships
17
+ **both themes**, follows `prefers-color-scheme` by default, and offers a manual override that persists.
18
+ Neither theme is the "real" one.
19
+
20
+ ## Tokens
21
+
22
+ Every value below is a CSS custom property in `:root`, re-declared under `[data-theme="dark"]` and
23
+ `@media (prefers-color-scheme: dark)`. Nothing in the stylesheet may hardcode a color.
24
+
25
+ | role | light | dark |
26
+ | --- | --- | --- |
27
+ | `--bg` canvas | `#f5f6f8` | `#0d0f12` |
28
+ | `--surface` cards, panels, bar | `#ffffff` | `#16191e` |
29
+ | `--surface-2` insets, fields | `#eef0f4` | `#1d2127` |
30
+ | `--surface-3` pressed, hovered inset | `#e7eaef` | `#252a32` |
31
+ | `--text` primary | `#10131a` | `#eceef2` |
32
+ | `--text-2` secondary | `#3f4653` | `#b9bfc9` |
33
+ | `--text-3` tertiary | `#5c6472` | `#949ba6` |
34
+ | `--text-4` quietest legible | `#6f7784` | `#7f8794` |
35
+ | `--line` hairline | `#e2e5ea` | `#262b33` |
36
+ | `--line-2` defined edge | `#cdd2da` | `#363c46` |
37
+ | `--wire` link stroke | `#99a1af` | `#4e5663` |
38
+
39
+ Every text token clears 4.5:1 against the surface it is used on, in both themes. `--text-4` is the floor,
40
+ not a decorative gray: there is no token below AA.
41
+
42
+ **One accent, locked:** `--accent` `#2563eb` / `#5b9cff`. It means *selected, primary, interactive*. It is
43
+ the only non-status hue on the surface.
44
+
45
+ **Three status hues, each a meaning:**
46
+
47
+ | token | meaning | light | dark |
48
+ | --- | --- | --- | --- |
49
+ | `--danger` | a link that goes nowhere | `#d92d20` | `#ff6b5e` |
50
+ | `--warn` | queued, proposed, not yet in the code | `#b25e09` | `#f0b132` |
51
+ | `--ok` | an entry point, a way in | `#067a5b` | `#35c99a` |
52
+
53
+ Adding a color means adding a row to the legend. There is no decorative color.
54
+
55
+ ## Link kind is drawn, not colored
56
+
57
+ A wire's **hue carries state** (normal, dead, queued, traced). A wire's **style carries kind**, so the four
58
+ navigation mechanisms stay distinguishable without spending four more hues:
59
+
60
+ | kind | stroke |
61
+ | --- | --- |
62
+ | anchor / button | solid |
63
+ | script navigation | dotted `1 4` |
64
+ | redirect | dashed `8 5` |
65
+ | form submit | solid, with a bar terminal at the source |
66
+ | queued (proposed) | dashed `7 5`, `--warn`, heavier |
67
+
68
+ ## Shape
69
+
70
+ One radius system, no exceptions:
71
+
72
+ - `--r-1` **6px** - every interactive control: buttons, tabs, inputs, chips, badges, list rows.
73
+ - `--r-2` **10px** - every surface: page cards, panels, popovers, the modal, the preview frame.
74
+ - `--r-3` **14px** - containers that hold surfaces: the section backdrop.
75
+
76
+ A pill, a circle, or a square-cornered control is a bug. The one intentional circle is the I/O port dot,
77
+ which is a dot because it is a point of connection.
78
+
79
+ ## Type
80
+
81
+ - `--ui`: the platform's own UI stack. Chrome, headings, labels, prose. No webfont: the viewer is served
82
+ from `node:http` with zero dependencies and must paint correctly offline, on the first frame.
83
+ - `--mono`: the platform's own mono stack. Reserved for **data**: routes, `file:line`, counts, link labels,
84
+ measurements. Mono here is not a costume for "technical" - every string set in it is an identifier or a
85
+ number the developer will compare against something else.
86
+
87
+ Hierarchy comes from weight and color, not scale. The size range across the whole UI is 11px to 19px.
88
+ Uppercase tracking appears in exactly two places: rail panel titles, and depth-column labels on the canvas.
89
+ Both are axis labels for a graph, not section eyebrows.
90
+
91
+ ## Motion
92
+
93
+ `prefers-reduced-motion` collapses all of it. The budget is deliberately small.
94
+
95
+ - **One authored moment:** on first paint the map settles in, cards rising 6px and fading, staggered by
96
+ depth column so the site's shape reads left to right. It plays once per render, never on scroll.
97
+ - Everything else is feedback: 120ms on hover and focus, 90ms on `:active`, a wire trace on hover.
98
+ - Nothing loops. Nothing parallaxes. A skeleton shimmer marks a preview that is genuinely still loading.
99
+
100
+ ## Density and honesty
101
+
102
+ The viewer is a cockpit, and it earns that density by never lying about it:
103
+
104
+ - Below `0.26` scale, wire labels and page thumbnails leave the render tree. A map that cannot show detail
105
+ says so by not drawing it, rather than compositing an unreadable smear.
106
+ - At most 12 preview iframes stay mounted, two loading at a time, page JS off by default. Offscreen cards
107
+ unmount.
108
+ - Past 60 pages, previews start off and the viewer says why.
109
+
110
+ ## Icons
111
+
112
+ 12px geometric glyphs from a single inline `<symbol>` sprite in `index.html`, one stroke weight (1.6),
113
+ `currentColor` throughout. An icon library is the right default and is deliberately not used here: the
114
+ project ships zero dependencies and no build step, so a CDN or an npm icon set would break both. That
115
+ constraint buys the sprite; it does not buy hand-drawn illustration. Every glyph is a rectangle, a line, a
116
+ circle, or a chevron.
117
+
118
+ No emoji anywhere in the UI.
119
+
120
+ ## Anti-reference
121
+
122
+ Two worlds this replaced, both evidence of what the tool does rather than authority over how it looks:
123
+
124
+ 1. A dark stone-and-torch "dungeon" theme with rooms, corridors, and quests.
125
+ 2. A periwinkle "starchart" theme with sky gradients, star specks, and violet hero cards.
126
+
127
+ Do not reintroduce a spatial metaphor, a decorative ground texture, pill controls, all-mono chrome, or a
128
+ saturated gradient card. The nouns in this tool are **page**, **link**, **section**, **entry**, **exit**,
129
+ **issue**, and **change**. They are the product's language, and they are the only language the UI speaks.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sitelines contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,336 @@
1
+ <div align="center">
2
+
3
+ # sitelines
4
+
5
+ **See every page on your site, every link between them, and everything that is broken.**
6
+
7
+ Zero dependencies · no build step · Node 18+ · MIT
8
+
9
+ </div>
10
+
11
+ ![The sitelines viewer showing a 20-page site laid out by click depth, with dead links in red](https://raw.githubusercontent.com/Jkaos725/sitelines/main/docs/map.jpg)
12
+
13
+ sitelines reads your source, finds every page and every link, button, redirect, and form that moves between
14
+ them, then serves a map in your browser. Each card is a real page with a live preview. Each wire is a real
15
+ control, labelled with its actual button text and traced back to `file:line`.
16
+
17
+ It tells you what is wrong: links that go nowhere, pages nothing links to, pages with no way out, pages
18
+ buried more than three clicks deep, and pages carrying too many exits.
19
+
20
+ You can also change the navigation from the map. Rename a button, point it somewhere else, draw a new link
21
+ between two pages, propose a new page. **Nothing is written to your site.** Changes queue to a JSON file,
22
+ and a coding agent implements them in your code when you ask.
23
+
24
+ > The screenshots on this page are the bundled example site, mapped by sitelines itself. Run
25
+ > `npx sitelines demo` and you get exactly this.
26
+
27
+ ---
28
+
29
+ ## How to use it
30
+
31
+ **1. Point it at your pages.**
32
+
33
+ ```bash
34
+ cd your-project
35
+ npx sitelines open
36
+ ```
37
+
38
+ That scans, starts a local server, and opens `http://localhost:4370`. It looks for your pages in `public`,
39
+ `site`, `www`, `static`, `src/pages`, `src/routes`, `src/app`, `app/pages`, `pages`, `app`, or `docs`.
40
+ Point it somewhere else with `--root`:
41
+
42
+ ```bash
43
+ npx sitelines open --root src/routes --port 5000
44
+ ```
45
+
46
+ **2. Read the map.** Columns are click depth from your front door. Cards are pages. Wires are controls. The
47
+ left sidebar counts what exists and lists what is broken, and every issue jumps you to the page it is on.
48
+
49
+ **3. Change the navigation.** Edit an exit's text, change where it points, draw a new link, propose a page.
50
+ Everything you do queues up instead of touching your files.
51
+
52
+ **4. Hand the queue to an agent.** Press **Copy prompt**, or install the skill and say
53
+ *"apply my sitelines changes"*.
54
+
55
+ ---
56
+
57
+ ## Install
58
+
59
+ Three ways in, depending on what you want.
60
+
61
+ ### Option 1: the tool
62
+
63
+ Everything above. The scanner, the server, and the map.
64
+
65
+ ```bash
66
+ npx sitelines open # npm
67
+ pnpm dlx sitelines open # pnpm
68
+ bunx sitelines open # bun
69
+ ```
70
+
71
+ Or keep it around:
72
+
73
+ ```bash
74
+ npm install -g sitelines
75
+ pnpm add -g sitelines
76
+ bun add -g sitelines
77
+ ```
78
+
79
+ **Every `npx` in this README works the same as `pnpm dlx` and `bunx`.** sitelines has no dependencies and
80
+ no build step, so there is nothing for a package manager to disagree about. It runs on Node 18+ and on Bun.
81
+
82
+ Or from source, with nothing to build:
83
+
84
+ ```bash
85
+ git clone https://github.com/Jkaos725/sitelines.git
86
+ cd your-project
87
+ node /path/to/sitelines/bin/sitelines.mjs open
88
+ ```
89
+
90
+ ### Option 2: the demo
91
+
92
+ See what it does before pointing it at anything of yours. This copies a 20-page example site into
93
+ `./sitelines-demo` and maps it. Nothing outside that folder is touched.
94
+
95
+ ```bash
96
+ npx sitelines demo
97
+ ```
98
+
99
+ The example has deliberate faults so every part of the map has something to show: two dead links (one of
100
+ them only reachable by reading the JavaScript), three orphans, a dead end, and a page four clicks deep.
101
+
102
+ ### Option 3: the Claude Code skill
103
+
104
+ The same tool, plus the instructions an agent needs to turn your queued changes into real code. This is the
105
+ only option that can actually **write** the changes for you.
106
+
107
+ **Claude Code** reads skill folders:
108
+
109
+ ```bash
110
+ npx sitelines skill install # ~/.claude/skills/sitelines (every project)
111
+ npx sitelines skill install --project # ./.claude/skills/sitelines (this repo only)
112
+ ```
113
+
114
+ **Codex, opencode, Cursor, Zed, Gemini CLI** read `AGENTS.md` from your project root:
115
+
116
+ ```bash
117
+ npx sitelines agents install # writes the block into ./AGENTS.md
118
+ npx sitelines agents uninstall # takes it back out
119
+ ```
120
+
121
+ That appends a fenced block rather than overwriting, so an existing `AGENTS.md` keeps everything it
122
+ already had. Re-running replaces the block in place instead of duplicating it, and uninstalling leaves
123
+ your own content untouched.
124
+
125
+ <details>
126
+ <summary>Install it by hand instead</summary>
127
+
128
+ A Claude Code skill is just a folder containing a `SKILL.md`. Copy four things into it:
129
+
130
+ ```bash
131
+ git clone https://github.com/Jkaos725/sitelines.git
132
+ mkdir -p ~/.claude/skills/sitelines
133
+
134
+ cp sitelines/skill/SKILL.md ~/.claude/skills/sitelines/
135
+ cp -r sitelines/skill/references ~/.claude/skills/sitelines/
136
+ cp -r sitelines/scripts ~/.claude/skills/sitelines/
137
+ cp -r sitelines/viewer ~/.claude/skills/sitelines/
138
+ ```
139
+
140
+ On Windows PowerShell:
141
+
142
+ ```powershell
143
+ git clone https://github.com/Jkaos725/sitelines.git
144
+ New-Item -ItemType Directory -Force "$HOME\.claude\skills\sitelines"
145
+
146
+ Copy-Item sitelines\skill\SKILL.md "$HOME\.claude\skills\sitelines\"
147
+ Copy-Item sitelines\skill\references "$HOME\.claude\skills\sitelines\" -Recurse
148
+ Copy-Item sitelines\scripts "$HOME\.claude\skills\sitelines\" -Recurse
149
+ Copy-Item sitelines\viewer "$HOME\.claude\skills\sitelines\" -Recurse
150
+ ```
151
+
152
+ </details>
153
+
154
+ <details>
155
+ <summary>Or ask Claude Code to install it</summary>
156
+
157
+ Paste this in:
158
+
159
+ > Install the sitelines skill from https://github.com/Jkaos725/sitelines into `~/.claude/skills/sitelines`.
160
+ > Clone the repo to a temp directory, copy `skill/SKILL.md`, `skill/references/`, `scripts/`, and `viewer/`
161
+ > into the skill folder, then confirm `~/.claude/skills/sitelines/SKILL.md` exists.
162
+
163
+ </details>
164
+
165
+ Restart Claude Code, then type `/sitelines` or just ask:
166
+
167
+ - "show me the site flow"
168
+ - "where does the Start free button go?"
169
+ - "find the dead links"
170
+ - "apply my sitelines changes"
171
+
172
+ ### All commands
173
+
174
+ ```
175
+ sitelines scan [--root DIR] [--out DIR] read the site, write .sitelines/flow.json
176
+ sitelines serve [--root DIR] [--out DIR] [--port N] [--open] open the map
177
+ sitelines open [--root DIR] [--port N] scan, serve, and open the browser
178
+ sitelines demo [--dir DIR] [--port N] copy the example site and map it
179
+ sitelines skill install [--global|--project] [--force] install the Claude Code skill
180
+ sitelines skill uninstall [--global|--project] remove it
181
+ sitelines agents install [--dir DIR] write the AGENTS.md block (every other agent)
182
+ sitelines agents uninstall [--dir DIR] remove just that block
183
+ sitelines version
184
+ sitelines help
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Features
190
+
191
+ ### It hides your header and footer, and says so
192
+
193
+ A nav bar repeated on twenty pages emits twenty identical links, and those wires bury every link that is
194
+ actually specific to a page. sitelines detects any link appearing on more than four pages, folds it away,
195
+ and reports the count it folded: **27 of 174** in the screenshot above. Turn it back on under **Layers**.
196
+
197
+ Folding is a drawing decision, never an analytical one. A page reachable only through your footer is still
198
+ reachable, and sitelines will not call it an orphan just because the footer is hidden.
199
+
200
+ ### Every page is live, at any width
201
+
202
+ ![The inspector panel open on a docs page, showing a live desktop preview and all eight of its exits with file and line numbers](https://raw.githubusercontent.com/Jkaos725/sitelines/main/docs/inspector.jpg)
203
+
204
+ Click a card to open it: a real preview at mobile, tablet, or desktop width, every exit with its editable
205
+ label and destination, every entrance, and a notes field. Previews come straight off disk, so you do not
206
+ need a dev server running.
207
+
208
+ The dots down each side of a card are ports, and each wire terminates on the exact dot that represents it.
209
+ Hover any dot or wire for the control's real text, both routes, how it navigates, and its `file:line`.
210
+
211
+ ### Changes queue instead of landing
212
+
213
+ ![The queued changes drawer listing five pending edits, with the map already previewing them](https://raw.githubusercontent.com/Jkaos725/sitelines/main/docs/changes.jpg)
214
+
215
+ | Do this | Get this |
216
+ | --- | --- |
217
+ | Edit an exit's text field | A queued rename |
218
+ | Change an exit's destination | A queued retarget |
219
+ | Click the ✕ on an exit | A queued removal |
220
+ | **+ Link**, then click a destination | A queued new control |
221
+ | **+ Page** | A queued new page, optionally linked from the selected one |
222
+
223
+ The map previews the result immediately: queued links draw in amber, and a proposed page appears as a
224
+ dashed card. The queue lives in `.sitelines/edits.json` and is the only thing sitelines writes.
225
+
226
+ ### Both themes, because it sits next to your editor
227
+
228
+ ![The same map in dark mode](https://raw.githubusercontent.com/Jkaos725/sitelines/main/docs/dark.jpg)
229
+
230
+ Follows `prefers-color-scheme` and remembers a manual override. Every text color clears WCAG AA in both.
231
+
232
+ ### Views are yours to define
233
+
234
+ The base view, **everything**, always shows every page and never takes a rule. Filter from it and sitelines
235
+ starts a new view rather than quietly turning your one complete picture into a partial one. Press **+** on
236
+ the tab bar to name a view, or write one into `.sitelines/views.json` and it appears:
237
+
238
+ ```json
239
+ {
240
+ "active": "all",
241
+ "views": [
242
+ { "id": "all", "label": "everything", "base": true, "include": [], "exclude": [] },
243
+ { "id": "docs", "label": "docs only", "include": ["/docs/**"], "exclude": [] },
244
+ { "id": "app", "label": "no legal", "include": [], "exclude": ["/legal/**"] }
245
+ ]
246
+ }
247
+ ```
248
+
249
+ Patterns: `/docs/**` (subtree), `/docs/` (exact), `/docs/*` (one level), `*report*` (substring).
250
+
251
+ ### Directories collapse
252
+
253
+ Pages sharing a top-level directory sit on a labelled backdrop. Click the label to fold the whole directory
254
+ into one card: internal links disappear, and the directory keeps one wire per neighbour labelled `N links`,
255
+ with every underlying control in its tooltip.
256
+
257
+ ### Keyboard
258
+
259
+ | | |
260
+ | --- | --- |
261
+ | Drag background / wheel | Pan / zoom |
262
+ | `f` | Fit everything on screen |
263
+ | `/` | Focus the filter |
264
+ | `1` `2` `3` | Switch views |
265
+ | `e` | New link from the selected page |
266
+ | `Escape` | Cancel whatever is in progress |
267
+
268
+ ---
269
+
270
+ ## What the scanner reads
271
+
272
+ | Source | How the label is found |
273
+ | --- | --- |
274
+ | `<a href>` | The link text |
275
+ | `<button>` / `<div>` with `onclick="location.href=…"`, `data-href`, `data-nav`, `formaction` | The element text |
276
+ | `<form action>` | `POST form` |
277
+ | `<meta http-equiv=refresh>` | `meta refresh` |
278
+ | `location.href` / `.assign` / `.replace`, `window.open`, `router.push` / `.replace`, `navigate()` | The nearest `getElementById` or `querySelector` above the call |
279
+
280
+ Pages are `*.html` files. If a project has none, sitelines falls back to framework page files under
281
+ `pages/`, `routes/`, or `app/`.
282
+
283
+ It is static analysis, so it reads what is in the source. A destination assembled at runtime from a template
284
+ string is not something a scanner can resolve, and sitelines skips it rather than guessing.
285
+
286
+ ## Previews
287
+
288
+ Two defaults worth knowing:
289
+
290
+ - **Page JavaScript is off.** Preview iframes get an empty `sandbox`. A page that polls or retries a dead
291
+ API will otherwise peg your browser forty iframes over. Markup and CSS still render. Turn it on under
292
+ **Layers → Run page JavaScript**.
293
+ - **Service workers are blocked** inside previews, or your site's own worker caches the preview responses
294
+ and serves them back for every later preview.
295
+
296
+ For pages that need real data or a login, run your own dev server and use the **Open** button on the card.
297
+
298
+ ## Performance
299
+
300
+ Large sites stay usable because the viewer refuses to pretend:
301
+
302
+ - At most 12 preview iframes stay mounted, two loading at a time. Offscreen cards unmount.
303
+ - Past 60 pages previews start off, and the viewer says why.
304
+ - Below 0.26 zoom, thumbnails and wire labels leave the render tree instead of compositing a smear.
305
+
306
+ ## State
307
+
308
+ sitelines writes one directory next to where you run it:
309
+
310
+ ```
311
+ .sitelines/
312
+ ├── flow.json the scan: pages, links, issues, plus your card positions and notes
313
+ ├── views.json your views and their rules
314
+ └── edits.json the change queue, including applied history
315
+ ```
316
+
317
+ Add it to `.gitignore` if it is yours alone, or commit it to share the layout and notes with your team.
318
+ Nothing outside this directory is ever written.
319
+
320
+ ---
321
+
322
+ ## Contributing
323
+
324
+ Issues and pull requests are welcome.
325
+
326
+ - **No dependencies.** Everything is Node's standard library and platform web APIs. A PR adding a runtime
327
+ dependency needs a strong reason.
328
+ - **No build step.** `viewer/` is HTML, CSS, and ES modules served as written.
329
+ - **Read `DESIGN.md` before touching `viewer/style.css`.** It is the authority on tokens, shape, type,
330
+ motion, and what each color means.
331
+ - Test against `examples/demo-site` and against something with more than 40 pages. Density is where this
332
+ tool succeeds or fails.
333
+
334
+ ## License
335
+
336
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+ // sitelines CLI - scan a site's navigation, then browse and edit it in a map.
3
+ import path from 'node:path';
4
+ import fs from 'node:fs';
5
+ import { spawn, spawnSync } from 'node:child_process';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const here = path.dirname(fileURLToPath(import.meta.url));
9
+ const pkgRoot = path.join(here, '..');
10
+ const scripts = path.join(pkgRoot, 'scripts');
11
+ const argv = process.argv.slice(2);
12
+ const cmd = argv[0];
13
+ const rest = argv.slice(1);
14
+
15
+ const version = (() => {
16
+ try { return JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8')).version; }
17
+ catch { return '0.0.0'; }
18
+ })();
19
+
20
+ const HELP = `sitelines ${version} - map, audit, and edit a site's navigation
21
+
22
+ sitelines scan [--root DIR] [--out DIR]
23
+ Read every page and every link between them. Writes .sitelines/flow.json.
24
+ --root defaults to the first of public, site, www, static, src/pages,
25
+ src/routes, src/app, app/pages, pages, app, docs that exists.
26
+
27
+ sitelines serve [--root DIR] [--out DIR] [--port N] [--open]
28
+ Open the map in a browser. Requires a scan first.
29
+
30
+ sitelines open [--root DIR] [--port N]
31
+ Scan, then serve, then open the browser. The usual entry point.
32
+
33
+ sitelines demo [--dir sitelines-demo] [--port N]
34
+ Copy the bundled 20-page example site into ./sitelines-demo, map it, and
35
+ open it. Nothing outside that directory is touched. Use this to see what
36
+ sitelines does before pointing it at your own project.
37
+
38
+ sitelines skill install [--global|--project] [--dir PATH] [--force]
39
+ sitelines skill uninstall [--global|--project] [--dir PATH]
40
+ Install sitelines as a Claude Code skill so /sitelines works and Claude can
41
+ apply the changes you queue in the map. --global is ~/.claude/skills,
42
+ --project is ./.claude/skills. Defaults to --global.
43
+
44
+ sitelines agents [install|uninstall] [--dir PATH]
45
+ Same instructions, as an AGENTS.md block in your project root. Codex,
46
+ opencode, Cursor, Zed and Gemini CLI read that file, so this is how those
47
+ agents learn to apply your queued changes. Appends to an existing
48
+ AGENTS.md rather than overwriting it.
49
+
50
+ sitelines version
51
+ sitelines help
52
+
53
+ State lives in .sitelines/ next to where you run it. Add it to .gitignore, or
54
+ commit it to share the map's layout and notes with your team.`;
55
+
56
+ function run(file, args, opts = {}) {
57
+ const r = spawnSync(process.execPath, [path.join(scripts, file), ...args], { stdio: 'inherit', ...opts });
58
+ if (r.error) { console.error(`sitelines: ${r.error.message}`); process.exit(1); }
59
+ return r.status ?? 0;
60
+ }
61
+
62
+ function flagValue(args, name, fallback) {
63
+ const i = args.indexOf(`--${name}`);
64
+ return i > -1 && args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1] : fallback;
65
+ }
66
+
67
+ function openBrowser(url) {
68
+ const cmds = process.platform === 'win32'
69
+ ? ['cmd', ['/c', 'start', '', url]]
70
+ : process.platform === 'darwin'
71
+ ? ['open', [url]]
72
+ : ['xdg-open', [url]];
73
+ try { spawn(cmds[0], cmds[1], { stdio: 'ignore', detached: true }).unref(); }
74
+ catch { /* no browser here; the URL is already printed */ }
75
+ }
76
+
77
+ switch (cmd) {
78
+ case 'scan':
79
+ process.exit(run('scan.mjs', rest));
80
+ break;
81
+
82
+ case 'serve': {
83
+ const wantOpen = rest.includes('--open');
84
+ const args = rest.filter((a) => a !== '--open');
85
+ if (wantOpen) setTimeout(() => openBrowser(`http://localhost:${flagValue(args, 'port', '4370')}`), 700);
86
+ process.exit(run('serve.mjs', args));
87
+ break;
88
+ }
89
+
90
+ case 'open': {
91
+ const code = run('scan.mjs', rest);
92
+ if (code !== 0) process.exit(code);
93
+ setTimeout(() => openBrowser(`http://localhost:${flagValue(rest, 'port', '4370')}`), 700);
94
+ process.exit(run('serve.mjs', rest));
95
+ break;
96
+ }
97
+
98
+ case 'demo': {
99
+ const dest = path.resolve(process.cwd(), flagValue(rest, 'dir', 'sitelines-demo'));
100
+ const src = path.join(pkgRoot, 'examples', 'demo-site');
101
+ if (!fs.existsSync(src)) { console.error(`sitelines: the bundled example is missing at ${src}`); process.exit(1); }
102
+ if (fs.existsSync(dest) && fs.readdirSync(dest).length) {
103
+ console.log(`sitelines: reusing the existing ${path.basename(dest)}/`);
104
+ } else {
105
+ fs.mkdirSync(dest, { recursive: true });
106
+ fs.cpSync(src, dest, { recursive: true });
107
+ console.log(`sitelines: copied the example site to ${path.basename(dest)}/ (20 pages)`);
108
+ }
109
+ const port = flagValue(rest, 'port', '4370');
110
+ // keep the demo's state inside the demo, so it never mixes with the map of
111
+ // whatever real project the user runs this from
112
+ const args = ['--root', dest, '--out', path.join(dest, '.sitelines'), '--port', port];
113
+ const code = run('scan.mjs', args);
114
+ if (code !== 0) process.exit(code);
115
+ console.log('sitelines: this example has deliberate faults - 2 dead links, 3 orphans, a dead end, and a page 4 clicks deep');
116
+ setTimeout(() => openBrowser(`http://localhost:${port}`), 700);
117
+ process.exit(run('serve.mjs', args));
118
+ break;
119
+ }
120
+
121
+ case 'skill': {
122
+ const sub = rest[0];
123
+ const args = rest.slice(1);
124
+ if (sub === 'install') process.exit(run('install-skill.mjs', args));
125
+ if (sub === 'uninstall') process.exit(run('install-skill.mjs', ['--uninstall', ...args]));
126
+ console.error('sitelines: expected `sitelines skill install` or `sitelines skill uninstall`');
127
+ process.exit(1);
128
+ break;
129
+ }
130
+
131
+ case 'agents': {
132
+ const sub = rest[0] === 'uninstall' ? ['--uninstall'] : [];
133
+ const args = rest.filter((a) => a !== 'install' && a !== 'uninstall');
134
+ process.exit(run('install-skill.mjs', ['--agents', ...sub, ...args]));
135
+ break;
136
+ }
137
+
138
+ case 'version':
139
+ case '--version':
140
+ case '-v':
141
+ console.log(version);
142
+ break;
143
+
144
+ case undefined:
145
+ case 'help':
146
+ case '--help':
147
+ case '-h':
148
+ console.log(HELP);
149
+ break;
150
+
151
+ default:
152
+ console.error(`sitelines: unknown command "${cmd}"\n`);
153
+ console.log(HELP);
154
+ process.exit(1);
155
+ }
@@ -0,0 +1,30 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>About | Meridian</title>
7
+ <link rel="stylesheet" href="../assets/site.css">
8
+ </head>
9
+ <body>
10
+ <header class="nav">
11
+ <a class="logo" href="/">Meridian</a>
12
+ <nav>
13
+ <a href="/product/">Product</a>
14
+ <a href="/docs/">Docs</a>
15
+ <a href="/pricing/">Pricing</a>
16
+ </nav>
17
+ <a class="cta" href="/signup/">Start free</a>
18
+ </header>
19
+ <main>
20
+ <h1>About</h1>
21
+ <p class="lead">Six people, one product, no roadmap theatre.</p>
22
+ <p><a href="/blog/">Read the blog</a> and <a href="/contact/">get in touch</a>.</p>
23
+ </main>
24
+ <footer class="foot">
25
+ <a href="/legal/privacy/">Privacy</a>
26
+ <a href="/legal/terms/">Terms</a>
27
+ <a href="https://github.com/">GitHub</a>
28
+ </footer>
29
+ </body>
30
+ </html>
@@ -0,0 +1,3 @@
1
+ document.getElementById('submit').addEventListener('click', () => {
2
+ location.href = '/app/';
3
+ });