n-seo 0.3.0 → 0.3.2

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.
Binary file
package/ops/doctor.py CHANGED
@@ -78,8 +78,35 @@ def check_tools():
78
78
  report("OK" if v >= (3, 10) else "FAIL",
79
79
  f"python {v.major}.{v.minor} ({Path(sys.executable).name})",
80
80
  "" if v >= (3, 10) else "3.10+ required")
81
- nm = seo_config.ROOT / "node_modules"
82
- report("OK" if nm.exists() else "WARN", "node_modules", "" if nm.exists() else "run: npm install")
81
+ # Ask node whether the engine can actually resolve its dependencies,
82
+ # rather than looking for a node_modules directory inside the engine.
83
+ # npm hoists dependencies to the *consumer's* node_modules, so a perfectly
84
+ # healthy `npm i n-seo` has none of its own — and the old check told those
85
+ # users to run `npm install`, which would not have helped. This is the
86
+ # same resolution the CLI uses to find tsx before it spawns the dashboard.
87
+ # tsx is resolved through its package.json because that is the exact
88
+ # lookup the CLI does before spawning the dashboard. hono and its server
89
+ # are resolved as bare specifiers: their `exports` maps deliberately do
90
+ # not expose ./package.json, so asking for it throws on a healthy install.
91
+ probe = (
92
+ "const {createRequire}=require('node:module');"
93
+ "const r=createRequire(process.argv[1]+'/');"
94
+ "r.resolve('tsx/package.json');"
95
+ "for (const d of ['hono','@hono/node-server']) r.resolve(d);"
96
+ )
97
+ try:
98
+ subprocess.run([node, "-e", probe, str(seo_config.ROOT)],
99
+ check=True, capture_output=True, timeout=30)
100
+ report("OK", "engine dependencies")
101
+ except FileNotFoundError:
102
+ report("WARN", "engine dependencies", "skipped — node not found")
103
+ except subprocess.TimeoutExpired:
104
+ report("WARN", "engine dependencies", "node timed out resolving them")
105
+ except subprocess.CalledProcessError:
106
+ hint = ("run: npm install" if (seo_config.ROOT / "package.json").exists()
107
+ and not (seo_config.ROOT / "node_modules").exists()
108
+ else "reinstall the engine: npm install n-seo")
109
+ report("FAIL", "engine dependencies", f"tsx/hono not resolvable — {hint}")
83
110
 
84
111
 
85
112
  def check_auth(cfg):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n-seo",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Agentic, local-first SEO / AEO / GEO control plane: pulls Search Console + GA4, probes your sites, and ranks the next moves. Your LLM writes the proposals; your coding agent works the queue over MCP.",
5
5
  "license": "MIT",
6
6
  "author": "En Dash Consulting (https://endash.us)",
package/public/styles.css CHANGED
@@ -21,6 +21,7 @@
21
21
  --topbar-ink: rgba(255, 255, 255, 0.78);
22
22
  --heat: 55, 87, 214;
23
23
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
24
+ --display: "Montserrat", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
24
25
 
25
26
  /* Traffic-source groups. Okabe-Ito derived: vermillion / indigo / green /
26
27
  pink stay separable under deuteranopia and protanopia, and no pair of
@@ -85,7 +86,7 @@ body {
85
86
  line-height: 1.55;
86
87
  }
87
88
  main { max-width: 1480px; margin: 0 auto; padding: 28px 24px 80px; }
88
- h1, h2, h3 { font-family: "Montserrat", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; line-height: 1.2; letter-spacing: -0.01em; }
89
+ h1, h2, h3 { font-family: var(--display); line-height: 1.2; letter-spacing: -0.01em; }
89
90
  h1 { font-size: 1.8rem; font-weight: 800; margin: 0.5em 0; }
90
91
  h2 { font-size: 1.05rem; font-weight: 700; margin: 2em 0 0.6em; text-transform: uppercase; letter-spacing: 0.06em; }
91
92
  h2 small { font-weight: 500; color: var(--muted); font-size: 0.75rem; text-transform: none; letter-spacing: 0; }
@@ -100,12 +101,13 @@ a { color: var(--accent); }
100
101
  padding: 11px 24px; background: var(--topbar);
101
102
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
102
103
  }
103
- .brand { display: flex; align-items: center; gap: 9px; font-weight: 800; color: #fff; text-decoration: none; letter-spacing: -0.01em; }
104
+ .brand { display: flex; align-items: center; gap: 9px; font-family: var(--display); font-weight: 800; color: #fff; text-decoration: none; letter-spacing: -0.01em; }
104
105
  .brand-mark { flex-shrink: 0; display: block; } /* the mark carries its own brand colors */
105
106
  .brand-name { max-width: 24ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
106
107
  .brand-sub { color: var(--topbar-ink); font-weight: 500; font-size: 0.8rem; }
107
108
  .topbar nav { display: flex; gap: 4px; flex-wrap: wrap; }
108
109
  .topbar nav a, .nav-menu summary {
110
+ font-family: var(--display);
109
111
  color: var(--topbar-ink); text-decoration: none; font-size: 0.82rem; font-weight: 600;
110
112
  padding: 4px 10px; border-radius: 99px;
111
113
  }