n-seo 0.6.0 → 0.6.1
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/CHANGELOG.md +20 -1
- package/ingest/__pycache__/analyze_metadata.cpython-312.pyc +0 -0
- package/ingest/__pycache__/google_auth.cpython-312.pyc +0 -0
- package/ingest/__pycache__/http_util.cpython-312.pyc +0 -0
- package/ingest/__pycache__/seo_config.cpython-312.pyc +0 -0
- package/ops/__pycache__/daily.cpython-312.pyc +0 -0
- package/ops/__pycache__/daily_diff.cpython-312.pyc +0 -0
- package/ops/__pycache__/demo_data.cpython-312.pyc +0 -0
- package/ops/__pycache__/doctor.cpython-312.pyc +0 -0
- package/ops/__pycache__/export_static.cpython-312.pyc +0 -0
- package/ops/__pycache__/llm.cpython-312.pyc +0 -0
- package/ops/__pycache__/publish.cpython-312.pyc +0 -0
- package/ops/__pycache__/update_check.cpython-312.pyc +0 -0
- package/ops/doctor.py +39 -2
- package/ops/llm.py +6 -1
- package/package.json +1 -1
- package/probes/__pycache__/site_probe.cpython-312.pyc +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,24 @@ uses [Semantic Versioning](https://semver.org/).
|
|
|
8
8
|
|
|
9
9
|
Nothing yet.
|
|
10
10
|
|
|
11
|
+
## [0.6.1] - 2026-09-15
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **The daily log threw away the reason a model call failed.** The llm module
|
|
15
|
+
reported `stderr` only, but a CLI may put its failure on stdout — `claude`
|
|
16
|
+
exits 1 and writes "Failed to authenticate" there — so the log read
|
|
17
|
+
`llm: exit 1:` with nothing after the colon. Every call, cause discarded.
|
|
18
|
+
It now falls back to stdout. This is the bug that matters here: it turned a
|
|
19
|
+
one-line diagnosis into a long one, and produced a confident wrong answer
|
|
20
|
+
along the way.
|
|
21
|
+
- **`doctor` reported a model command as healthy when it could not run.** The
|
|
22
|
+
check was `shutil.which`: the file exists, therefore fine. A revoked key, a
|
|
23
|
+
lapsed subscription or an unreadable credential store all leave the binary
|
|
24
|
+
exactly where it was. It now sends a trivial prompt and reports what came
|
|
25
|
+
back. Note the limitation — it reports what happens *in the environment you
|
|
26
|
+
run it in*. A stripped environment that cannot reach the system credential
|
|
27
|
+
store will fail this check while the scheduled job, which can, is fine.
|
|
28
|
+
|
|
11
29
|
## [0.6.0] - 2026-09-15
|
|
12
30
|
|
|
13
31
|
### Added
|
|
@@ -486,7 +504,8 @@ First public release.
|
|
|
486
504
|
`n-seo upgrade` gate — without changing the reported test counts, and an
|
|
487
505
|
in-place install ran those assertions against the owner's live data.
|
|
488
506
|
|
|
489
|
-
[Unreleased]: https://github.com/en-dash-consulting/n-seo/compare/v0.6.
|
|
507
|
+
[Unreleased]: https://github.com/en-dash-consulting/n-seo/compare/v0.6.1...HEAD
|
|
508
|
+
[0.6.1]: https://github.com/en-dash-consulting/n-seo/compare/v0.6.0...v0.6.1
|
|
490
509
|
[0.6.0]: https://github.com/en-dash-consulting/n-seo/compare/v0.5.0...v0.6.0
|
|
491
510
|
[0.5.0]: https://github.com/en-dash-consulting/n-seo/compare/v0.4.1...v0.5.0
|
|
492
511
|
[0.4.1]: https://github.com/en-dash-consulting/n-seo/compare/v0.4.0...v0.4.1
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/ops/doctor.py
CHANGED
|
@@ -79,6 +79,36 @@ def check_profile(cfg):
|
|
|
79
79
|
report("OK", f"from {d}")
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def probe_llm(cmd):
|
|
83
|
+
"""Actually call the model and see whether it answers.
|
|
84
|
+
|
|
85
|
+
`shutil.which` only proves a file exists. An expired OAuth session, a
|
|
86
|
+
revoked key or a missing subscription all leave the binary exactly where
|
|
87
|
+
it was, so the old check reported OK while every call in the daily run
|
|
88
|
+
failed — eleven of them, silently, because the llm module degrades rather
|
|
89
|
+
than failing the run. A green tick for something that cannot work is
|
|
90
|
+
worse than no check at all.
|
|
91
|
+
|
|
92
|
+
Returns (ok, detail). Never raises.
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
p = subprocess.run(cmd, input="Reply with the single word: ok",
|
|
96
|
+
capture_output=True, text=True, timeout=90, encoding="utf-8")
|
|
97
|
+
except subprocess.TimeoutExpired:
|
|
98
|
+
return False, "timed out after 90s"
|
|
99
|
+
except OSError as exc:
|
|
100
|
+
return False, str(exc)[:120]
|
|
101
|
+
if p.returncode != 0:
|
|
102
|
+
detail = (p.stderr or p.stdout or "").strip().splitlines()
|
|
103
|
+
return False, (detail[0][:160] if detail else f"exit {p.returncode}, no output")
|
|
104
|
+
out = (p.stdout or "").strip()
|
|
105
|
+
if not out:
|
|
106
|
+
# Some CLIs report an auth failure on stdout and still exit 0.
|
|
107
|
+
err = (p.stderr or "").strip().splitlines()
|
|
108
|
+
return False, (err[0][:160] if err else "exited 0 but said nothing")
|
|
109
|
+
return True, out.splitlines()[0][:60]
|
|
110
|
+
|
|
111
|
+
|
|
82
112
|
def check_config():
|
|
83
113
|
print("config")
|
|
84
114
|
if seo_config.using_example():
|
|
@@ -301,8 +331,15 @@ def check_modules(cfg):
|
|
|
301
331
|
elif key == "command" and not http and not llm.get("http"):
|
|
302
332
|
report("FAIL", "llm has neither http nor command configured")
|
|
303
333
|
continue
|
|
304
|
-
|
|
305
|
-
|
|
334
|
+
if not shutil.which(cmd[0]):
|
|
335
|
+
report("FAIL", f"llm.{key}: {cmd[0]}", "not on PATH")
|
|
336
|
+
continue
|
|
337
|
+
if OFFLINE:
|
|
338
|
+
report("OK", f"llm.{key}: {cmd[0]}", "on PATH — not called (offline)")
|
|
339
|
+
continue
|
|
340
|
+
ok, detail = probe_llm(cmd)
|
|
341
|
+
report("OK" if ok else "FAIL", f"llm.{key}: {raw[:48]}",
|
|
342
|
+
detail if not ok else f"answered: {detail}")
|
|
306
343
|
hn = cfg["modules"].get("hackerNews", {})
|
|
307
344
|
if hn.get("enabled"):
|
|
308
345
|
user = (hn.get("user") or "").strip()
|
package/ops/llm.py
CHANGED
|
@@ -155,7 +155,12 @@ def infer(prompt: str, fast: bool = False) -> str | None:
|
|
|
155
155
|
print(f" llm: {exc}", file=sys.stderr)
|
|
156
156
|
return None
|
|
157
157
|
if p.returncode != 0:
|
|
158
|
-
|
|
158
|
+
# Not every CLI reports its failure on stderr. `claude` exits 1 and
|
|
159
|
+
# writes "Failed to authenticate: OAuth session expired" to stdout,
|
|
160
|
+
# so logging stderr alone produced "llm: exit 1:" with nothing after
|
|
161
|
+
# it — eleven times in one run, with the reason thrown away each time.
|
|
162
|
+
detail = (p.stderr.strip() or p.stdout.strip() or "no output")
|
|
163
|
+
print(f" llm: exit {p.returncode}: {detail[:200]}", file=sys.stderr)
|
|
159
164
|
return None
|
|
160
165
|
out = p.stdout.strip()
|
|
161
166
|
return out or None
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n-seo",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
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)",
|
|
Binary file
|