n-seo 0.2.0 → 0.3.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/.claude/skills/n-seo-add-site/SKILL.md +125 -0
  2. package/.claude/skills/n-seo-deploy/SKILL.md +171 -0
  3. package/.claude/skills/n-seo-review/SKILL.md +106 -0
  4. package/.claude/skills/n-seo-setup/SKILL.md +139 -0
  5. package/.claude/skills/n-seo-ship/SKILL.md +122 -0
  6. package/.claude/skills/n-seo-triage/SKILL.md +90 -0
  7. package/.claude/skills/orient/SKILL.md +54 -0
  8. package/README.md +93 -15
  9. package/bin/n-seo.mjs +163 -6
  10. package/docs/ADDING-A-SITE.md +4 -0
  11. package/docs/DEPLOY.md +4 -0
  12. package/docs/FAQ.md +32 -3
  13. package/docs/INSTANCE.md +54 -0
  14. package/docs/PRD.md +23 -5
  15. package/docs/SCHEDULING.md +4 -0
  16. package/docs/SETUP-GOOGLE.md +7 -0
  17. package/ingest/__pycache__/analyze_metadata.cpython-312.pyc +0 -0
  18. package/ingest/__pycache__/google_auth.cpython-312.pyc +0 -0
  19. package/ingest/__pycache__/http_util.cpython-312.pyc +0 -0
  20. package/ingest/__pycache__/seo_config.cpython-312.pyc +0 -0
  21. package/ops/__pycache__/daily.cpython-312.pyc +0 -0
  22. package/ops/__pycache__/daily_diff.cpython-312.pyc +0 -0
  23. package/ops/__pycache__/demo_data.cpython-312.pyc +0 -0
  24. package/ops/__pycache__/export_static.cpython-312.pyc +0 -0
  25. package/ops/__pycache__/llm.cpython-312.pyc +0 -0
  26. package/ops/__pycache__/publish.cpython-312.pyc +0 -0
  27. package/ops/demo_data.py +6 -0
  28. package/ops/doctor.py +9 -0
  29. package/ops/export_static.py +5 -0
  30. package/package.json +10 -2
  31. package/probes/__pycache__/site_probe.cpython-312.pyc +0 -0
  32. package/public/fonts/OFL.txt +105 -0
  33. package/public/fonts/montserrat-latin-var.woff2 +0 -0
  34. package/public/styles.css +18 -1
  35. package/src/server.tsx +22 -1
  36. package/src/views.tsx +70 -6
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: n-seo-triage
3
+ description: Answer "what should I work on today" from an n-seo instance — the ranked queue, the last run, and the scan proposals, with the evidence and the operating rules applied. Use for any status or what-next question.
4
+ ---
5
+
6
+ # Triage — what to do today
7
+
8
+ Read the system's outputs. **Do not re-run pulls, re-crawl sites, or
9
+ recompute trends to answer a status question** — the daily run already
10
+ produced all of it, and a re-run spends quota to reproduce a file that exists.
11
+
12
+ ```sh
13
+ E=<engine checkout>/bin/n-seo.mjs
14
+ I=<instance dir>
15
+ PORT=$(python3 -c "import json;print(json.load(open('$I/n-seo.config.json'))['port'])" 2>/dev/null || echo 4600)
16
+ ```
17
+
18
+ ## 1. Trust the data before you use it
19
+
20
+ ```sh
21
+ cat "$I/data/last-run.json" # ts, failures, per-step timing
22
+ ```
23
+
24
+ Or the MCP `ops_status` tool, which adds dataset staleness and probe results.
25
+ If `failures` is non-empty, **say so first** — the numbers below are stale in
26
+ whatever the failing step feeds. `data/daily-ops.log` has the reason.
27
+
28
+ ## 2. Read the queue
29
+
30
+ ```sh
31
+ curl -s "http://localhost:$PORT/api/actions"
32
+ ```
33
+
34
+ Or MCP `list_actions` (`status: active`, optional `host`, `tag`, `limit`),
35
+ which returns the same ranked list. If the dashboard is not running, start it
36
+ (`node $E start --instance "$I"`) or read `config/backlog.json` plus the
37
+ rule-derived cards from the MCP server.
38
+
39
+ Each action carries `impact`, `effort`, `why` (the evidence), `how`, `spec`,
40
+ `tag`, and `watching` when it has shipped. Ranking is impact ÷ effort weight.
41
+
42
+ ## 3. Read what happened lately
43
+
44
+ ```sh
45
+ tail -30 "$I/docs/daily-log.md" # or MCP daily_log
46
+ cat "$I/data/opportunity-proposals.json" # or MCP opportunity_proposals
47
+ ```
48
+
49
+ The log gives probe health, the watched pages' numbers, the conversions line
50
+ and cross-referrals. The proposals file gives rising queries no card covers,
51
+ machine proposals awaiting accept, and verdicts on watching items.
52
+
53
+ ## 4. Apply the rules before recommending
54
+
55
+ - **28-day freeze.** A page whose title or description changed inside 28 days
56
+ is off limits for another metadata change. Check `shippedWatch` in
57
+ `config/backlog.json` and the dated `watching` notes for the page.
58
+ - **≈8 metadata changes a week across all sites.** Count what has already
59
+ shipped this week from the recent `watching` notes before proposing more.
60
+ If the audit lists twenty, recommend the top few, not the list.
61
+ - **Impact orders, it does not forecast.** Never present an impact number as
62
+ an expected result. "Ranked highest" not "will earn 60 clicks."
63
+ - **Decisions ride the 90 days.** Cite the 90-day figures the cards use, not
64
+ 16-month totals.
65
+ - **Proposals are not queue items** until accepted.
66
+
67
+ ## 5. Answer
68
+
69
+ Short, ranked, evidence attached. For each recommendation:
70
+
71
+ - the move, in one line;
72
+ - the numbers behind it, from the card's `why`;
73
+ - why now (freeze expired, riser, regression, seasonal window);
74
+ - what it costs (`effort`).
75
+
76
+ Then, separately and briefly:
77
+
78
+ - **Blocked or broken** — failing steps, probe regressions, index problems.
79
+ - **Waiting on the owner** — scan proposals to accept or ignore, drafts whose
80
+ status contains `approval`.
81
+ - **Watching** — shipped work whose window is closing, and what the data says
82
+ so far. Verdicts of *succeeded* or *failed* are review triggers; hand them
83
+ to `/n-seo-review` rather than acting on them here.
84
+
85
+ ## Stay inside the lines
86
+
87
+ - Do not write to `config/backlog.json` here. Triage reads.
88
+ - Do not draft comments for Hacker News or Reddit. The digests brief; the
89
+ human writes. Point at the briefing and stop.
90
+ - Do not enable modules or change settings as part of answering.
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: orient
3
+ description: Fast, cheap orientation for a fresh session on this n-seo install. Use FIRST in any new session here — reads current state from the running system instead of re-deriving it from raw data.
4
+ ---
5
+
6
+ # Orient — get current in ~4 reads, not 40
7
+
8
+ The system is self-updating; your job is to read its outputs, never to rebuild
9
+ its conclusions. Procedure, in order — stop when you have what the task needs:
10
+
11
+ 1. **The queue is the truth for "what's next"**:
12
+ `curl -s http://localhost:4600/api/actions` (the port is `port` in
13
+ `n-seo.config.json`; the MCP `list_actions` tool returns the same).
14
+ Every active and watching action with evidence and spec.
15
+ 2. **What happened lately**: the last entry of `docs/daily-log.md` (probe
16
+ health, watched-page numbers, conversions line) and `data/last-run.json`
17
+ (did the last daily run succeed, which steps failed).
18
+ 3. **Machine-proposed work awaiting the owner**:
19
+ `data/opportunity-proposals.json` — proposals and verdicts from the scan.
20
+ 4. **Recent human decisions**: `git log --oneline -10` in this repo.
21
+
22
+ ## Do NOT (wastes tokens, re-derives what the batch already did)
23
+
24
+ - Do not re-run pulls, audits, or trend analysis to answer status questions —
25
+ `ops/daily.py` refreshes everything on its schedule; its outputs are in
26
+ `data/` and `docs/daily-log.md`.
27
+ - Do not re-audit sites or re-crawl pages the probe already covers.
28
+ - Do not summarize the architecture from source — `docs/ARCHITECTURE.md` has it.
29
+ - Do not restate history — `docs/daily-log.md` and `git log` hold it.
30
+
31
+ ## Rules live in CLAUDE.md — the ones people forget
32
+
33
+ Queue edits only through accept/watch/retire or explicit approval · never
34
+ generate comment text · 28-day title freeze + ≤8 metadata changes/week ·
35
+ decisions ride the 90-day window · proposals never self-promote.
36
+
37
+ ## Then hand off to the right skill
38
+
39
+ `n-seo-triage` for "what should I do today", `n-seo-ship` to implement one
40
+ card, `n-seo-review` for the weekly pass, `n-seo-setup` and `n-seo-add-site`
41
+ for configuration. `.claude/skills/README.md` says which skills are for
42
+ operating an instance and which are for developing the engine.
43
+
44
+ ## If something is broken
45
+
46
+ - Dashboard down: `npm start` in-place, or
47
+ `node <engine>/bin/n-seo.mjs start --instance <dir>` in engine+instance
48
+ mode, or if it runs as a service,
49
+ `launchctl kickstart -k gui/$(id -u)/n-seo.dashboard`.
50
+ - Daily run failing: the Logs page (`/logs`) or `data/daily-ops.log`, then
51
+ `python3 ops/doctor.py`. 401/403 from Google means the service account
52
+ lost access or the key file moved — see `docs/SETUP-GOOGLE.md`.
53
+ - Nothing on the dashboard: no run has happened yet — `python3 ops/daily.py`
54
+ (or `python3 ops/demo_data.py` to see it populated with synthetic data).
package/README.md CHANGED
@@ -24,8 +24,10 @@ to **a model you choose**, and everything that changes a site is handed to you.
24
24
  ships with n-seo. Leave it off and you still get the full data-derived queue.
25
25
  - **Your coding agent, on the queue.** A read-only MCP server exposes the same
26
26
  data the dashboard reads, so Claude Code — or any MCP client — can answer
27
- "what should I do first this week?" and then go implement it. Six skills ship
28
- with it, so the operating rules are enforced rather than merely documented.
27
+ "what should I do first this week?" and then go implement it. Seven skills
28
+ are installed into your instance, so setup, triage and shipping are things
29
+ you ask for in plain language, with the operating rules enforced rather than
30
+ merely documented.
29
31
  - **It proposes; it never acts.** No module edits a site, sends an email or
30
32
  posts a comment. Machine proposals wait in a holding area until you accept
31
33
  them. Your data stays on your hardware: nothing leaves the host except the
@@ -35,11 +37,61 @@ to **a model you choose**, and everything that changes a site is handed to you.
35
37
 
36
38
  ## Quickstart (5 minutes, no Google setup)
37
39
 
40
+ > **n-seo is its own project — do not install it inside a website's repo.**
41
+ > It reads your sites the way Google sees them, through the Search Console and
42
+ > GA4 APIs, so it never needs a copy of their code. `n-seo init` makes one
43
+ > directory of its own and everything it writes stays there.
44
+
45
+ **✗ Inside a site's repo** — what people do by accident:
46
+
47
+ ```
48
+ ~/code/my-website/
49
+ ├── .git/
50
+ ├── package.json
51
+ ├── src/
52
+ └── my-sites/ ← init ran here
53
+ ├── n-seo.config.json committed to your website
54
+ ├── config/backlog.json committed
55
+ ├── .env committed
56
+ └── data/ rewritten every morning,
57
+ committed, then deployed
58
+ ```
59
+
60
+ **✓ Beside it** — one instance, watching every site you own:
61
+
62
+ ```
63
+ ~/
64
+ ├── code/
65
+ │ ├── my-website/ untouched
66
+ │ └── docs-site/ untouched
67
+ └── my-sites/ ← n-seo init my-sites
68
+ ├── n-seo.config.json lists both sites
69
+ ├── config/backlog.json your action queue
70
+ ├── content/ drafts, campaigns
71
+ ├── .claude/skills/ ask, don't read docs
72
+ └── data/ gitignored, regenerable
73
+ ```
74
+
75
+ `n-seo init` refuses the first layout: it stops when the target holds a
76
+ `package.json`, or when the directory it would create falls inside a git
77
+ repository that is not itself an instance, and prints the command you wanted
78
+ instead. `--force` overrides it.
79
+
80
+ **What it touches.** Your site repos: never — when you ship a fix it is you,
81
+ in your repo, on a branch. Google's APIs: read-only, through a service account
82
+ you create. Its own directory: everything it writes. Delete that directory and
83
+ nothing else on your machine changes.
84
+
38
85
  ```sh
39
- npm install -g n-seo
40
- n-seo init my-sites && cd my-sites
41
- n-seo demo # a synthetic dataset for example.com
42
- n-seo start # dashboard http://localhost:4600
86
+ npm install -g n-seo # the engine, once — a CLI, not a dependency
87
+
88
+ cd ~ # stand somewhere that is NOT a site repo
89
+ n-seo init my-sites # creates ./my-sites: config, queue, content,
90
+ cd my-sites # .mcp.json and seven skills
91
+
92
+ n-seo demo # synthetic data, so every page has something
93
+ n-seo start # the dashboard, reading this directory
94
+ # → http://localhost:4600
43
95
  ```
44
96
 
45
97
  Open http://localhost:4600. Every page is populated from the demo data, so you
@@ -48,6 +100,29 @@ removes it.
48
100
 
49
101
  Prefer not to install globally? `npx n-seo init my-sites` works the same way.
50
102
 
103
+ ### Then talk to it
104
+
105
+ `n-seo init` installs seven skills into the new directory, so the rest of the
106
+ setup is a conversation rather than a docs crawl. Open it in Claude Code — or
107
+ any agent that reads `.claude/skills/` — and say what you want:
108
+
109
+ ```
110
+ cd my-sites && claude
111
+
112
+ "set this up for my sites" → /n-seo-setup: config, the Google service
113
+ account, both console grants, first run
114
+ "add learn-pretext.com" → /n-seo-add-site
115
+ "what should I work on today?" → /n-seo-triage: the queue, with evidence
116
+ "do the first one" → /n-seo-ship: a branch and a PR in the
117
+ site's own repo, freeze rules enforced
118
+ "how did last month go?" → /n-seo-review
119
+ ```
120
+
121
+ A read-only MCP server is registered in the generated `.mcp.json`, so the agent
122
+ reads the same queue and metrics the dashboard shows. It can propose and
123
+ implement; it cannot publish, post, or change a site behind your back. Details
124
+ in [Agentic by design](#agentic-by-design).
125
+
51
126
  <details>
52
127
  <summary>Or run it from a clone, if you want to change the engine itself</summary>
53
128
 
@@ -59,10 +134,11 @@ npm run demo
59
134
  npm start
60
135
  ```
61
136
 
62
- In this mode the config, queue and data live inside the checkout. That is the
63
- right shape for hacking on n-seo; for running it, the instance layout above
64
- keeps your files separate from the engine so upgrades are a reinstall rather
65
- than a merge. See [docs/INSTANCE.md](docs/INSTANCE.md).
137
+ In this mode the config, queue and data live inside the checkout still a
138
+ standalone project of its own, just one you can edit. That is the right shape
139
+ for hacking on n-seo; for running it, the instance layout above keeps your
140
+ files separate from the engine so upgrades are a reinstall rather than a
141
+ merge. See [docs/INSTANCE.md](docs/INSTANCE.md).
66
142
  </details>
67
143
 
68
144
  ## Connect your real sites
@@ -149,8 +225,10 @@ Desktop, stdio and authenticated HTTP clients are covered in
149
225
  working here has to follow.
150
226
 
151
227
  Skills ship for the work itself, so the rules are enforced rather than
152
- merely documented six that operate an instance, plus `orient` to get
153
- current in a fresh session:
228
+ merely documented. `n-seo init` copies all seven into the instance's
229
+ `.claude/skills/` with this install's real paths substituted in, alongside a
230
+ `CLAUDE.md` carrying the operating rules — so an agent opened in that
231
+ directory is oriented before you type anything:
154
232
 
155
233
  | Skill | Use it when |
156
234
  |---|---|
@@ -163,9 +241,9 @@ current in a fresh session:
163
241
  | `orient` | First thing in a fresh session — get current in a few reads |
164
242
 
165
243
  `n-seo-ship` stops rather than crossing the 28-day title freeze or the weekly
166
- metadata budget. `.claude/skills/README.md` explains which skills operate an
167
- instance and which are contributor tooling for developing the engine with
168
- [n-dx](https://n-dx.dev).
244
+ metadata budget. The engine checkout carries a second set of skills (`ndx-*`)
245
+ for developing n-seo itself with [n-dx](https://n-dx.dev); those are not
246
+ copied into an instance. `.claude/skills/README.md` explains the split.
169
247
 
170
248
  Read-only is the point: an agent can reason over your search data all day and
171
249
  still cannot bypass the freeze, the batching, or you.
package/bin/n-seo.mjs CHANGED
@@ -28,7 +28,9 @@ const USAGE = `n-seo ${PKG.version} — an SEO on your machine
28
28
 
29
29
  usage: n-seo <command> [--instance <dir>] [args...]
30
30
 
31
- init [dir] scaffold an instance directory (default: cwd); never overwrites
31
+ init [dir] scaffold a standalone instance directory (default: cwd); never
32
+ overwrites, and refuses to scaffold inside a website's repo
33
+ (--force overrides)
32
34
  start run the dashboard for the instance
33
35
  dev dashboard with reload on engine code changes
34
36
  daily the morning run (args pass through: --only, --skip, --list, …)
@@ -142,8 +144,87 @@ function missingDeps(cmd, what) {
142
144
 
143
145
  /* ---------- init ---------- */
144
146
 
145
- function init(dirArg) {
147
+ /** Files that mean "this directory is an application", not a place to keep
148
+ * search-ops files. n-seo is a standalone project: it writes a config, a
149
+ * queue, drafts and a data/ tree that the daily run rewrites every morning.
150
+ * Dropped into a website's repo those files get committed, deployed, and
151
+ * eventually served — and `n-seo upgrade` starts fighting the site's own
152
+ * package.json. The instance sits beside the sites it watches, never inside
153
+ * one. */
154
+ const APP_MARKERS = [
155
+ "package.json", "index.html", "next.config.js", "next.config.mjs", "next.config.ts",
156
+ "vite.config.js", "vite.config.ts", "astro.config.mjs", "nuxt.config.ts",
157
+ "svelte.config.js", "gatsby-config.js", "remix.config.js", "angular.json",
158
+ "Gemfile", "go.mod", "Cargo.toml", "pyproject.toml", "composer.json",
159
+ "hugo.toml", "config.toml", "_config.yml", "wp-config.php", "Dockerfile",
160
+ ];
161
+
162
+ /** The nearest ancestor holding a .git, or null. */
163
+ function gitRootOf(dir) {
164
+ let cur = path.resolve(dir);
165
+ for (;;) {
166
+ if (fs.existsSync(path.join(cur, ".git"))) return cur;
167
+ const up = path.dirname(cur);
168
+ if (up === cur) return null;
169
+ cur = up;
170
+ }
171
+ }
172
+
173
+ /** Why this directory is the wrong place for an instance, or null if it is
174
+ * fine.
175
+ *
176
+ * `forChild` means the instance will be a NEW directory created inside
177
+ * `dir`, which changes one answer: a repository root is a fine place to put
178
+ * an instance (it becomes its own repo) but a terrible parent for one (the
179
+ * new directory lands inside that repo's working tree).
180
+ *
181
+ * Re-running init on an existing instance is always allowed. */
182
+ function wrongPlaceFor(dir, forChild = false) {
183
+ if (fs.existsSync(path.join(dir, "n-seo.config.json"))) return null;
184
+ // In-place mode: the engine checkout is the instance. It has a package.json
185
+ // and a .git of its own, both of which would otherwise trip every check.
186
+ if (!forChild && path.resolve(dir) === ROOT) return null;
187
+ const marker = APP_MARKERS.find((f) => fs.existsSync(path.join(dir, f)));
188
+ if (marker) {
189
+ return forChild
190
+ ? `${dir} contains ${marker} — an instance created here would sit inside an application`
191
+ : `${dir} contains ${marker} — that is an application, not an n-seo instance`;
192
+ }
193
+ const root = gitRootOf(dir);
194
+ const insideSomeoneElsesRepo = root && (forChild || root !== path.resolve(dir));
195
+ if (insideSomeoneElsesRepo && !fs.existsSync(path.join(root, "n-seo.config.json"))) {
196
+ return `${dir} is inside the git repository at ${root} — the instance would be committed to it`;
197
+ }
198
+ return null;
199
+ }
200
+
201
+ function init(dirArg, force = false) {
146
202
  const dir = path.resolve(dirArg ?? process.cwd());
203
+ // A directory that does not exist yet inherits its parent's problem: a new
204
+ // folder created inside a site repo is still inside that site repo.
205
+ const wrong = fs.existsSync(dir) ? wrongPlaceFor(dir) : wrongPlaceFor(path.dirname(dir), true);
206
+ if (wrong && !force) {
207
+ console.error(`n-seo init: refusing to scaffold here.
208
+
209
+ ${wrong}
210
+
211
+ n-seo is a standalone project, not something you add to a website. It keeps a
212
+ config, an action queue, content drafts and a data/ tree that the daily run
213
+ rewrites every morning; inside a site repo those get committed and deployed.
214
+ The instance watches your sites over the Search Console API — it never needs
215
+ to live in their code.
216
+
217
+ Do this instead:
218
+
219
+ cd ~ # anywhere outside your site repos
220
+ n-seo init my-sites
221
+ cd my-sites
222
+
223
+ Then list the sites you own in my-sites/n-seo.config.json.
224
+
225
+ If you really mean it here, pass --force.`);
226
+ return 2;
227
+ }
147
228
  fs.mkdirSync(dir, { recursive: true });
148
229
  const name = path.basename(dir);
149
230
  const put = (rel, content) => {
@@ -170,6 +251,59 @@ function init(dirArg) {
170
251
  const src = path.join(ROOT, rel);
171
252
  put(rel, fs.existsSync(src) ? fs.readFileSync(src, "utf8") : `# ${rel}\n`);
172
253
  }
254
+ // The skills are the product's front door: with them in place the instance
255
+ // is a directory an agent can be pointed at, and setup / triage / shipping
256
+ // become a conversation instead of a docs-reading exercise. The engine's
257
+ // own contributor skills (ndx-*) stay behind — they are for working on
258
+ // n-seo, not with it. Placeholders become this install's real paths so the
259
+ // commands in them are copy-pasteable.
260
+ const INSTANCE_SKILLS = ["orient", "n-seo-setup", "n-seo-add-site", "n-seo-triage", "n-seo-ship", "n-seo-review", "n-seo-deploy"];
261
+ let skillsCopied = 0;
262
+ for (const skill of INSTANCE_SKILLS) {
263
+ const src = path.join(ROOT, ".claude", "skills", skill, "SKILL.md");
264
+ if (!fs.existsSync(src)) continue;
265
+ const body = fs
266
+ .readFileSync(src, "utf8")
267
+ .replaceAll("<engine checkout>/bin/n-seo.mjs", path.join(ROOT, "bin", "n-seo.mjs"))
268
+ .replaceAll("<instance dir>", dir);
269
+ put(path.join(".claude", "skills", skill, "SKILL.md"), body);
270
+ skillsCopied++;
271
+ }
272
+
273
+ put("CLAUDE.md", `# ${name} — an n-seo instance
274
+
275
+ This directory is an n-seo instance: config, the curated action queue, content
276
+ drafts, and the data the daily run writes. It is **not** a website. The sites
277
+ it watches live in their own repositories and are read over the Search Console
278
+ and GA4 APIs.
279
+
280
+ Engine (the code): ${ROOT}
281
+ Docs: ${path.join(ROOT, "docs")}
282
+
283
+ ## Working here
284
+
285
+ Skills in \`.claude/skills/\` cover the routine work — start with
286
+ \`/orient\` in a fresh session, then \`/n-seo-triage\` for what to do today,
287
+ \`/n-seo-ship\` to implement one card, \`/n-seo-review\` for the weekly pass.
288
+
289
+ ## Rules that are not negotiable
290
+
291
+ - **28-day metadata freeze.** After a page's title or description changes,
292
+ leave that page's metadata alone for 28 days. Title churn reads as
293
+ manipulation and resets Google's evaluation.
294
+ - **At most ~8 title/description changes a week** across all sites.
295
+ - **Impact numbers order the queue. They are not forecasts.** Never report
296
+ one as an expected result.
297
+ - **Decisions ride the trailing 90 days.** The 16-month data is for totals
298
+ and history only.
299
+ - **Shipped work becomes \`watching\`, never deleted** — that is how the loop
300
+ closes.
301
+ - **Machine proposals never self-promote.** They wait in the holding area on
302
+ /actions until a human accepts them.
303
+ - **Site changes ship as branches and pull requests** in the site's own repo,
304
+ never committed straight to its main branch.
305
+ `);
306
+
173
307
  const envExample = path.join(ROOT, ".env.example");
174
308
  put(".env", fs.existsSync(envExample) ? fs.readFileSync(envExample, "utf8") : "");
175
309
  put(".gitignore", "data/\nsite/\n.env\n__pycache__/\nnode_modules/\n");
@@ -203,15 +337,38 @@ n-seo upgrade # update the engine; your files here are untouched
203
337
 
204
338
  Edit \`n-seo.config.json\` for sites, Google access, modules and hooks.
205
339
  Docs: ${path.join(ROOT, "docs")}
340
+
341
+ ## Or just ask
342
+
343
+ n-seo is built to be driven by an AI agent. Open this directory in Claude Code
344
+ (or any agent that reads \`.claude/skills/\`) and talk to it:
345
+
346
+ "set this up for my sites" → /n-seo-setup
347
+ "what should I work on today?" → /n-seo-triage
348
+ "do the first one" → /n-seo-ship
349
+ "how did last month go?" → /n-seo-review
350
+
351
+ A read-only MCP server is wired up in \`.mcp.json\`, so the agent reads the
352
+ same queue and metrics the dashboard shows. It can propose and implement;
353
+ it cannot publish, post, or change your sites behind your back.
206
354
  `);
207
355
  console.log(`
208
356
  instance ready at ${dir}
357
+ a standalone project — your websites stay in their own repos, untouched
209
358
 
210
359
  next:
211
360
  cd ${dir}
212
- edit n-seo.config.json (sites, google.serviceAccountKey see ${path.join(ROOT, "docs", "SETUP-GOOGLE.md")})
213
- n-seo doctor
214
- n-seo demo && n-seo start (or n-seo daily once the key is in place)
361
+ n-seo demo && n-seo start see the dashboard working on synthetic data
362
+ edit n-seo.config.json your sites + google.serviceAccountKey
363
+ n-seo doctor checks the setup and names what is missing
364
+
365
+ or hand it to an agent — ${skillsCopied} skills are installed in .claude/skills/:
366
+ cd ${dir} && claude
367
+ "set this up for my sites" → /n-seo-setup walks the whole thing, including
368
+ the Google service account and both grants
369
+ later: "what should I work on today?" → /n-seo-triage
370
+
371
+ setup guide: ${path.join(ROOT, "docs", "SETUP-GOOGLE.md")}
215
372
  `);
216
373
  return 0;
217
374
  }
@@ -264,7 +421,7 @@ let code = 0;
264
421
 
265
422
  switch (cmd) {
266
423
  case "init":
267
- code = init(rest[0] ?? flag ?? process.cwd());
424
+ code = init(rest.find((a) => !a.startsWith("-")) ?? flag ?? process.cwd(), rest.includes("--force"));
268
425
  break;
269
426
  case "start":
270
427
  case "dev":
@@ -3,6 +3,10 @@
3
3
  One entry in `n-seo.config.json`. There are no per-script site lists:
4
4
  every pull, probe, audit, page and export reads the same `sites` array.
5
5
 
6
+ > Or say *"add example.com"* in the instance directory: the `n-seo-add-site`
7
+ > skill picks the right property form, finds the numeric GA4 id, proposes a
8
+ > brand regex, and reminds you about both access grants.
9
+
6
10
  ```json
7
11
  {
8
12
  "host": "docs.example.com",
package/docs/DEPLOY.md CHANGED
@@ -7,6 +7,10 @@ it sleeps, it travels, and the run silently skips a day.
7
7
 
8
8
  This document is about moving it to a box that is always on.
9
9
 
10
+ > Or say *"get this off my laptop"* in the instance directory — the
11
+ > `n-seo-deploy` skill runs this procedure, and refuses to expose the
12
+ > dashboard port to the internet whatever you ask it for.
13
+
10
14
  ## The shape
11
15
 
12
16
  One host runs two long-lived things — the dashboard and a scheduler that
package/docs/FAQ.md CHANGED
@@ -5,6 +5,25 @@ Nothing. The Search Console and GA4 APIs are free within quotas that ordinary
5
5
  use never approaches. The optional LLM module runs whatever local command you
6
6
  give it; if that is a paid CLI, that is your cost, not the tool's.
7
7
 
8
+ **Can I install it inside my website's repository?**
9
+ No — and `n-seo init` refuses to, so you cannot do it by accident. n-seo is a
10
+ standalone project. It writes a config, an action queue, content drafts and a
11
+ `data/` tree that the daily run rewrites every morning; inside a site repo all
12
+ of that gets committed, and usually deployed. It reads your sites through the
13
+ Search Console and GA4 APIs, so it has no reason to sit in their code. Put the
14
+ instance anywhere else — `~/my-sites` is fine — and list the sites you own in
15
+ its config. One instance can watch as many sites as you like. `--force`
16
+ overrides the check if you have a reason.
17
+
18
+ **Do I have to read all these docs?**
19
+ No. `n-seo init` installs seven skills into your instance, so setup and daily
20
+ use are a conversation: open the directory in Claude Code (or any agent that
21
+ reads `.claude/skills/`) and say "set this up for my sites", "what should I
22
+ work on today?", "do the first one". The agent reads the queue over a
23
+ read-only MCP server, follows the operating rules in the generated
24
+ `CLAUDE.md`, and cannot publish or change a site on its own. The docs are the
25
+ reference behind the skills, not a prerequisite.
26
+
8
27
  **Does it change my site?**
9
28
  No. It reads your data, probes your pages, and produces a queue of actions
10
29
  with evidence and specs. You make the change in your own repo and ship it.
@@ -89,9 +108,19 @@ shown flat rather than omitted so you can tell "connected and quiet" from
89
108
  "missing from the pipeline."
90
109
 
91
110
  **The indexing page lists a "Soft 404" for a page that is fine.**
92
- Check the "last crawled" column. A verdict is only as current as the crawl
93
- behind it; the page flags verdicts older than 90 days as stale. Request
94
- indexing and re-check rather than chasing a template bug.
111
+ Check the "last crawled" column first. A verdict is only as current as the
112
+ crawl behind it, and the page flags verdicts older than 90 days as stale — a
113
+ page rewritten since then is being judged on content Google has not seen. For
114
+ a stale verdict, request indexing once and let the recrawl settle it.
115
+
116
+ For a *fresh* soft 404, requesting indexing is the wrong move: Google already
117
+ fetched the page, got a 200, and decided the content was an error or empty.
118
+ Asking again re-runs the same judgement and spends the daily quota. Fetch the
119
+ URL yourself and pick the real cause — the page renders its content in
120
+ JavaScript and the crawler saw an empty shell, or it is genuinely thin, or it
121
+ is an error page returning 200 and should return 404 or 410 and leave the
122
+ sitemap. Fix that, then request indexing. n-seo's indexing page names the
123
+ check to run for each verdict.
95
124
 
96
125
  **A daily step failed. What now?**
97
126
  Open `/logs` (or `data/daily-ops.log`), then run `n-seo doctor`. A 401
package/docs/INSTANCE.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Running n-seo as an engine + instance
2
2
 
3
+ > **An instance is a standalone project.** It is never a subdirectory of a
4
+ > website you are optimising. n-seo reads your sites through the Search
5
+ > Console and GA4 APIs; it has no reason to live in their code, and living
6
+ > there means committing and deploying its config, its queue and a `data/`
7
+ > tree that is rewritten every morning.
8
+
9
+ ```
10
+ ~/
11
+ ├── code/
12
+ │ ├── my-website/ a site you own — untouched
13
+ │ └── docs-site/ another one — untouched
14
+ └── my-sites/ the instance
15
+ ├── n-seo.config.json both sites listed here
16
+ ├── config/backlog.json the curated queue
17
+ ├── content/ drafts, campaigns
18
+ ├── .claude/skills/ the operating skills
19
+ ├── CLAUDE.md the operating rules
20
+ └── data/ the daily run's output, gitignored
21
+ ```
22
+
23
+ One instance watches as many sites as you own, and `n-seo init` refuses to
24
+ scaffold into an application directory or a git repository it does not own
25
+ (`--force` overrides).
26
+
3
27
  n-seo can run two ways. In the simplest, you clone the repo, put your config
4
28
  in the checkout and run it there. In the second, the checkout is an **engine**
5
29
  you never edit, and everything that is yours — config, queue, content, data,
@@ -93,6 +117,36 @@ and `content/campaigns/README.md`, `.env` (from `.env.example`), a
93
117
  `.gitignore` that excludes `data/`, `site/` and `.env`, a `.mcp.json` that
94
118
  spawns the engine's MCP server with `N_SEO_INSTANCE` set, and a short README.
95
119
 
120
+ It also makes the instance agent-ready, which is how most people should drive
121
+ it:
122
+
123
+ - `.claude/skills/` — the seven operating skills (`orient`, `n-seo-setup`,
124
+ `n-seo-add-site`, `n-seo-triage`, `n-seo-ship`, `n-seo-review`,
125
+ `n-seo-deploy`), copied from the engine with this install's real engine and
126
+ instance paths substituted in, so every command in them is copy-pasteable.
127
+ The engine's contributor skills (`ndx-*`) are not copied — they are for
128
+ developing n-seo, not operating it.
129
+ - `CLAUDE.md` — the operating rules an agent working in this directory has to
130
+ follow: the 28-day freeze, the weekly metadata budget, impact-is-not-a-
131
+ forecast, decisions on the 90-day window, shipped-becomes-watching,
132
+ proposals never self-promote, site changes ship as pull requests.
133
+
134
+ With those two files plus `.mcp.json`, opening the instance in Claude Code is
135
+ enough: it can read the queue and the metrics, and it already knows the rules.
136
+
137
+ ```sh
138
+ cd ~/sites/search-ops && claude
139
+ "set this up for my sites" → /n-seo-setup
140
+ "what should I work on today?" → /n-seo-triage
141
+ "do the first one" → /n-seo-ship
142
+ "how did last month go?" → /n-seo-review
143
+ ```
144
+
145
+ Both files are yours once written. `n-seo init` never overwrites, so editing
146
+ `CLAUDE.md` to add your own rules survives every re-run and every engine
147
+ upgrade. To pick up improved skills from a newer engine, delete the ones you
148
+ have not customised and re-run `n-seo init`.
149
+
96
150
  To make `n-seo` a command, link the engine once:
97
151
 
98
152
  ```sh