kvseo 0.6.6__tar.gz
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.
- kvseo-0.6.6/.github/workflows/ci.yml +32 -0
- kvseo-0.6.6/.github/workflows/release.yml +46 -0
- kvseo-0.6.6/.gitignore +42 -0
- kvseo-0.6.6/LICENSE +21 -0
- kvseo-0.6.6/PKG-INFO +150 -0
- kvseo-0.6.6/README.md +108 -0
- kvseo-0.6.6/alembic.ini +44 -0
- kvseo-0.6.6/pyproject.toml +92 -0
- kvseo-0.6.6/src/kvseo/__init__.py +18 -0
- kvseo-0.6.6/src/kvseo/__main__.py +8 -0
- kvseo-0.6.6/src/kvseo/cli/__init__.py +56 -0
- kvseo-0.6.6/src/kvseo/cli/_util.py +27 -0
- kvseo-0.6.6/src/kvseo/cli/advisor.py +131 -0
- kvseo-0.6.6/src/kvseo/cli/audit.py +163 -0
- kvseo-0.6.6/src/kvseo/cli/config.py +18 -0
- kvseo-0.6.6/src/kvseo/cli/connect.py +166 -0
- kvseo-0.6.6/src/kvseo/cli/cost.py +108 -0
- kvseo-0.6.6/src/kvseo/cli/init.py +29 -0
- kvseo-0.6.6/src/kvseo/cli/report.py +103 -0
- kvseo-0.6.6/src/kvseo/config/__init__.py +1 -0
- kvseo-0.6.6/src/kvseo/config/paths.py +43 -0
- kvseo-0.6.6/src/kvseo/config/secrets.py +60 -0
- kvseo-0.6.6/src/kvseo/config/settings.py +75 -0
- kvseo-0.6.6/src/kvseo/connectors/__init__.py +5 -0
- kvseo-0.6.6/src/kvseo/connectors/base.py +52 -0
- kvseo-0.6.6/src/kvseo/connectors/csv.py +273 -0
- kvseo-0.6.6/src/kvseo/connectors/gsc.py +306 -0
- kvseo-0.6.6/src/kvseo/connectors/gsc_auth.py +96 -0
- kvseo-0.6.6/src/kvseo/connectors/psi.py +247 -0
- kvseo-0.6.6/src/kvseo/core/__init__.py +5 -0
- kvseo-0.6.6/src/kvseo/core/advisor/__init__.py +1 -0
- kvseo-0.6.6/src/kvseo/core/advisor/client.py +369 -0
- kvseo-0.6.6/src/kvseo/core/advisor/context.py +164 -0
- kvseo-0.6.6/src/kvseo/core/advisor/prompts.py +99 -0
- kvseo-0.6.6/src/kvseo/core/advisor/schemas.py +73 -0
- kvseo-0.6.6/src/kvseo/core/audit/__init__.py +1 -0
- kvseo-0.6.6/src/kvseo/core/audit/checks/__init__.py +24 -0
- kvseo-0.6.6/src/kvseo/core/audit/checks/_base.py +37 -0
- kvseo-0.6.6/src/kvseo/core/audit/checks/content.py +75 -0
- kvseo-0.6.6/src/kvseo/core/audit/checks/cwv.py +66 -0
- kvseo-0.6.6/src/kvseo/core/audit/checks/headings.py +34 -0
- kvseo-0.6.6/src/kvseo/core/audit/checks/meta.py +69 -0
- kvseo-0.6.6/src/kvseo/core/audit/checks/title.py +40 -0
- kvseo-0.6.6/src/kvseo/core/audit/document.py +139 -0
- kvseo-0.6.6/src/kvseo/core/audit/engine.py +198 -0
- kvseo-0.6.6/src/kvseo/core/audit/fetcher.py +94 -0
- kvseo-0.6.6/src/kvseo/core/audit/scoring.py +31 -0
- kvseo-0.6.6/src/kvseo/core/cost.py +105 -0
- kvseo-0.6.6/src/kvseo/core/report/__init__.py +1 -0
- kvseo-0.6.6/src/kvseo/core/report/renderer.py +193 -0
- kvseo-0.6.6/src/kvseo/core/report/templates/report.html.j2 +305 -0
- kvseo-0.6.6/src/kvseo/core/report/templates/report.md.j2 +96 -0
- kvseo-0.6.6/src/kvseo/py.typed +0 -0
- kvseo-0.6.6/src/kvseo/storage/__init__.py +1 -0
- kvseo-0.6.6/src/kvseo/storage/db.py +76 -0
- kvseo-0.6.6/src/kvseo/storage/migrations/env.py +57 -0
- kvseo-0.6.6/src/kvseo/storage/migrations/script.py.mako +26 -0
- kvseo-0.6.6/src/kvseo/storage/migrations/versions/0001_initial_schema.py +212 -0
- kvseo-0.6.6/src/kvseo/storage/models.py +256 -0
- kvseo-0.6.6/src/kvseo/storage/timestamps.py +24 -0
- kvseo-0.6.6/src/kvseo/storage/types.py +39 -0
- kvseo-0.6.6/tests/advisor/test_client.py +244 -0
- kvseo-0.6.6/tests/advisor/test_context.py +69 -0
- kvseo-0.6.6/tests/advisor/test_schemas.py +63 -0
- kvseo-0.6.6/tests/audit/test_engine.py +114 -0
- kvseo-0.6.6/tests/audit/test_fetcher.py +34 -0
- kvseo-0.6.6/tests/checks/test_checks.py +110 -0
- kvseo-0.6.6/tests/conftest.py +133 -0
- kvseo-0.6.6/tests/connectors/test_base.py +29 -0
- kvseo-0.6.6/tests/connectors/test_connect.py +81 -0
- kvseo-0.6.6/tests/connectors/test_csv.py +166 -0
- kvseo-0.6.6/tests/connectors/test_gsc.py +171 -0
- kvseo-0.6.6/tests/connectors/test_gsc_auth.py +23 -0
- kvseo-0.6.6/tests/connectors/test_psi.py +140 -0
- kvseo-0.6.6/tests/cost/test_cost.py +147 -0
- kvseo-0.6.6/tests/fixtures/csv/queries_bom_nopage.csv +3 -0
- kvseo-0.6.6/tests/fixtures/csv/queries_happy.csv +4 -0
- kvseo-0.6.6/tests/fixtures/csv/queries_mismatch.csv +3 -0
- kvseo-0.6.6/tests/fixtures/csv/queries_mostly_bad.csv +5 -0
- kvseo-0.6.6/tests/fixtures/csv/queries_outofrange.csv +6 -0
- kvseo-0.6.6/tests/fixtures/csv/queries_partial.csv +5 -0
- kvseo-0.6.6/tests/fixtures/csv/queries_renamed.csv +3 -0
- kvseo-0.6.6/tests/fixtures/html/good.html +26 -0
- kvseo-0.6.6/tests/fixtures/html/poor.html +14 -0
- kvseo-0.6.6/tests/fixtures/psi/example_mobile.json +36 -0
- kvseo-0.6.6/tests/report/test_renderer.py +103 -0
- kvseo-0.6.6/tests/storage/test_migrations.py +80 -0
- kvseo-0.6.6/tests/storage/test_models.py +92 -0
- kvseo-0.6.6/tests/test_cli.py +22 -0
- kvseo-0.6.6/tests/test_config_paths.py +25 -0
- kvseo-0.6.6/tests/test_init.py +52 -0
- kvseo-0.6.6/tests/test_secrets.py +46 -0
- kvseo-0.6.6/tests/unit/test_scoring.py +38 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [dev, main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [dev, main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: ${{ matrix.os }} / py${{ matrix.python-version }}
|
|
12
|
+
runs-on: ${{ matrix.os }}
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
17
|
+
python-version: ["3.12", "3.13"]
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
- name: Install
|
|
24
|
+
run: |
|
|
25
|
+
python -m pip install --upgrade pip
|
|
26
|
+
pip install -e ".[dev]"
|
|
27
|
+
- name: Lint (ruff)
|
|
28
|
+
run: ruff check .
|
|
29
|
+
- name: Type-check (mypy)
|
|
30
|
+
run: mypy
|
|
31
|
+
- name: Test (pytest)
|
|
32
|
+
run: pytest -q
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Tag-triggered PyPI publish via OIDC trusted publishing — no tokens at rest.
|
|
4
|
+
# Flow: /bump → sync main → `git tag vX.Y.Z` → `git push origin vX.Y.Z`.
|
|
5
|
+
# One-time setup (kVadrum, on PyPI): configure a Trusted Publisher / pending
|
|
6
|
+
# publisher for project `kvseo`, owner `kVadrum`, repo `kvseo`, workflow
|
|
7
|
+
# `release.yml`, environment `pypi`. See STATE.md "Publishing".
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
tags:
|
|
12
|
+
- 'v*.*.*'
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: release-${{ github.ref }}
|
|
16
|
+
cancel-in-progress: false
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
publish:
|
|
20
|
+
name: Build and publish to PyPI
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
environment: pypi
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
id-token: write # OIDC: lets PyPI mint a short-lived publish credential
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
- uses: actions/setup-python@v5
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.12"
|
|
31
|
+
- name: Verify tag matches package version
|
|
32
|
+
run: |
|
|
33
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
34
|
+
pkg="$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")"
|
|
35
|
+
echo "tag=$tag pyproject=$pkg"
|
|
36
|
+
[ "$tag" = "$pkg" ] || { echo "::error::tag v$tag does not match pyproject version $pkg"; exit 1; }
|
|
37
|
+
- name: Build sdist + wheel
|
|
38
|
+
run: |
|
|
39
|
+
python -m pip install --upgrade build
|
|
40
|
+
python -m build
|
|
41
|
+
- name: Check artifacts render on PyPI
|
|
42
|
+
run: |
|
|
43
|
+
python -m pip install --upgrade twine
|
|
44
|
+
twine check dist/*
|
|
45
|
+
- name: Publish to PyPI
|
|
46
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
kvseo-0.6.6/.gitignore
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# --- Local-only dev files (never committed — workspace policy) ---
|
|
2
|
+
CLAUDE.md
|
|
3
|
+
STATE.md
|
|
4
|
+
.claude/
|
|
5
|
+
|
|
6
|
+
# --- Internal spec & design docs (never committed — workspace policy) ---
|
|
7
|
+
# Numbered handoff/spec series (00-handoff.md … 10-risks…) + decision records.
|
|
8
|
+
[0-9][0-9]-*.md
|
|
9
|
+
adrs/
|
|
10
|
+
|
|
11
|
+
# --- Python ---
|
|
12
|
+
__pycache__/
|
|
13
|
+
*.py[cod]
|
|
14
|
+
*$py.class
|
|
15
|
+
*.egg-info/
|
|
16
|
+
.eggs/
|
|
17
|
+
build/
|
|
18
|
+
dist/
|
|
19
|
+
.venv/
|
|
20
|
+
venv/
|
|
21
|
+
env/
|
|
22
|
+
.python-version
|
|
23
|
+
|
|
24
|
+
# --- Tooling caches ---
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.mypy_cache/
|
|
27
|
+
.ruff_cache/
|
|
28
|
+
.coverage
|
|
29
|
+
htmlcov/
|
|
30
|
+
.tox/
|
|
31
|
+
|
|
32
|
+
# --- Local data / secrets ---
|
|
33
|
+
.env
|
|
34
|
+
*.db
|
|
35
|
+
*.sqlite
|
|
36
|
+
*.sqlite3
|
|
37
|
+
*.sqlite3-wal
|
|
38
|
+
*.sqlite3-shm
|
|
39
|
+
|
|
40
|
+
# --- OS cruft ---
|
|
41
|
+
.DS_Store
|
|
42
|
+
Thumbs.db
|
kvseo-0.6.6/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 KeMeK Network
|
|
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.
|
kvseo-0.6.6/PKG-INFO
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kvseo
|
|
3
|
+
Version: 0.6.6
|
|
4
|
+
Summary: An AI-native SEO copilot for solo operators and small agencies — the AI layer over your existing SEO stack.
|
|
5
|
+
Project-URL: Homepage, https://github.com/kvadrum/kvseo
|
|
6
|
+
Project-URL: Repository, https://github.com/kvadrum/kvseo
|
|
7
|
+
Author: kVadrum
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ai,audit,cli,core-web-vitals,llm,search-console,seo
|
|
11
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Requires-Dist: alembic>=1.13
|
|
20
|
+
Requires-Dist: google-auth-oauthlib>=1.2
|
|
21
|
+
Requires-Dist: google-auth>=2.29
|
|
22
|
+
Requires-Dist: httpx>=0.27
|
|
23
|
+
Requires-Dist: jinja2>=3.1
|
|
24
|
+
Requires-Dist: keyring>=25
|
|
25
|
+
Requires-Dist: litellm<2,>=1.40
|
|
26
|
+
Requires-Dist: markdown>=3.6
|
|
27
|
+
Requires-Dist: platformdirs>=4.2
|
|
28
|
+
Requires-Dist: pydantic-settings>=2.3
|
|
29
|
+
Requires-Dist: pydantic>=2.7
|
|
30
|
+
Requires-Dist: selectolax>=0.3.21
|
|
31
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
32
|
+
Requires-Dist: typer>=0.12
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
38
|
+
Requires-Dist: types-markdown; extra == 'dev'
|
|
39
|
+
Provides-Extra: pdf
|
|
40
|
+
Requires-Dist: playwright>=1.44; extra == 'pdf'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# kvseo
|
|
44
|
+
|
|
45
|
+
**The AI layer over your existing SEO stack.**
|
|
46
|
+
|
|
47
|
+
[](https://www.python.org/)
|
|
48
|
+
[](./LICENSE)
|
|
49
|
+
[](https://kvadrum.com)
|
|
50
|
+
|
|
51
|
+
`kvseo` is an AI-native SEO copilot for solo operators and small agencies. It
|
|
52
|
+
doesn't replace your data tools — it sits on top of them. Point it at a URL and
|
|
53
|
+
it runs an on-page audit, folds in Core Web Vitals and Search Console context,
|
|
54
|
+
and produces a prioritized, *reasoned* action list plus a client-ready report.
|
|
55
|
+
|
|
56
|
+
The data layer is a commodity. What `kvseo` adds is the layer the others skip:
|
|
57
|
+
**synthesis, AI-assisted prioritization, and agency-shaped reporting** — as a
|
|
58
|
+
fast, local, single-command CLI.
|
|
59
|
+
|
|
60
|
+
> **Status: v0.1 feature-complete.** The full pipeline (audit → advise → report)
|
|
61
|
+
> runs end-to-end and is code-reviewed, type-checked, and tested throughout. Not
|
|
62
|
+
> yet on PyPI — install from source today (see [Install](#install)); the tagged
|
|
63
|
+
> release is imminent.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Quickstart
|
|
68
|
+
|
|
69
|
+
Once installed (see [Install](#install)):
|
|
70
|
+
|
|
71
|
+
```console
|
|
72
|
+
$ kvseo init # create config + a local SQLite database
|
|
73
|
+
$ kvseo audit https://example.com # on-page audit + Core Web Vitals → HTML report
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
That loop needs **no API keys and no accounts**. `audit` writes a self-contained
|
|
77
|
+
HTML report to the current directory and prints the findings to your terminal.
|
|
78
|
+
Add data sources and an LLM to go deeper:
|
|
79
|
+
|
|
80
|
+
```console
|
|
81
|
+
$ kvseo connect psi --api-key <key> # PageSpeed Insights (free; raises CWV limits)
|
|
82
|
+
$ kvseo connect csv export.csv --site https://example.com/ # import a GSC CSV export — no OAuth
|
|
83
|
+
$ kvseo connect gsc # …or connect Search Console via OAuth
|
|
84
|
+
$ export ANTHROPIC_API_KEY=… # bring your own LLM key (any LiteLLM provider)
|
|
85
|
+
$ kvseo audit https://example.com # now with GSC context + an AI action list
|
|
86
|
+
$ kvseo cost # what the advisor spent, by provider / model
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## What it does
|
|
90
|
+
|
|
91
|
+
- **On-page audit** — 19 checks across title, meta, headings, content, schema,
|
|
92
|
+
images, and internal links, rolled into a single 0–100 score.
|
|
93
|
+
- **Core Web Vitals** — CrUX field data + Lighthouse lab metrics via the free
|
|
94
|
+
PageSpeed Insights API (works keyless at lower rate limits).
|
|
95
|
+
- **Search Console context** — real query / click / impression / position data
|
|
96
|
+
per URL, via OAuth **or** a plain CSV export (the no-API escape hatch).
|
|
97
|
+
- **AI advisor** — a prioritized "what to do next" list where every
|
|
98
|
+
recommendation is traceable to a specific finding. **Bring your own LLM key**
|
|
99
|
+
(Anthropic, OpenAI, Gemini, or a local Ollama — anything LiteLLM speaks to).
|
|
100
|
+
kvseo never proxies your key or marks up tokens, and `kvseo cost` shows exactly
|
|
101
|
+
what each run spent.
|
|
102
|
+
- **Reports** — a self-contained, single-file HTML report (inline CSS, embedded
|
|
103
|
+
data, zero external requests, print-to-PDF friendly) plus clean Markdown.
|
|
104
|
+
PDF/DOCX land in v0.2.
|
|
105
|
+
|
|
106
|
+
Everything **degrades gracefully**: the audit and HTML report work with no LLM
|
|
107
|
+
key at all — the advisor is an optional layer you unlock by adding a provider
|
|
108
|
+
key. No PSI key → the Core Web Vitals checks skip; no Search Console → the audit
|
|
109
|
+
still scores the page.
|
|
110
|
+
|
|
111
|
+
## Principles
|
|
112
|
+
|
|
113
|
+
- **BYO-key.** kvseo never ships, proxies, or marks up an LLM key.
|
|
114
|
+
- **Local-first.** Everything lives in one SQLite file on your machine — no
|
|
115
|
+
account, no cloud.
|
|
116
|
+
- **Read-only.** kvseo pulls from APIs you authorize; it never writes upstream.
|
|
117
|
+
- **No telemetry.** No phone-home, no usage metrics — not unless you opt in.
|
|
118
|
+
- **Reproducible.** Every audit and advisor run is stored and re-runnable; the
|
|
119
|
+
advisor works from stored data, so you can re-prioritize without re-fetching.
|
|
120
|
+
|
|
121
|
+
## Install
|
|
122
|
+
|
|
123
|
+
Requires **Python 3.12+**.
|
|
124
|
+
|
|
125
|
+
```console
|
|
126
|
+
# from PyPI (once published):
|
|
127
|
+
pipx install kvseo
|
|
128
|
+
|
|
129
|
+
# from source (works today):
|
|
130
|
+
git clone https://github.com/kVadrum/kvseo && cd kvseo
|
|
131
|
+
python3.12 -m venv .venv && . .venv/bin/activate
|
|
132
|
+
pip install -e ".[dev]"
|
|
133
|
+
kvseo --version
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Config lives in `~/.config/kvseo/` and data in `~/.local/share/kvseo/` (XDG;
|
|
137
|
+
`%APPDATA%` / `%LOCALAPPDATA%` on Windows). Override either with the
|
|
138
|
+
`KVSEO_CONFIG_DIR` / `KVSEO_DATA_DIR` environment variables.
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
[MIT](./LICENSE) — free to use, modify, and distribute. KeMeK Network © 2026.
|
|
143
|
+
|
|
144
|
+
### Trademarks
|
|
145
|
+
|
|
146
|
+
The "kvseo" and "kVadrum" names are trademarks of KeMeK Network. They are not
|
|
147
|
+
covered by the MIT license, and no rights to use the names are granted by this
|
|
148
|
+
repository. Independent forks must replace the brand name with their own.
|
|
149
|
+
|
|
150
|
+
A [kVadrum](https://kvadrum.com) Lab project, built in public.
|
kvseo-0.6.6/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# kvseo
|
|
2
|
+
|
|
3
|
+
**The AI layer over your existing SEO stack.**
|
|
4
|
+
|
|
5
|
+
[](https://www.python.org/)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](https://kvadrum.com)
|
|
8
|
+
|
|
9
|
+
`kvseo` is an AI-native SEO copilot for solo operators and small agencies. It
|
|
10
|
+
doesn't replace your data tools — it sits on top of them. Point it at a URL and
|
|
11
|
+
it runs an on-page audit, folds in Core Web Vitals and Search Console context,
|
|
12
|
+
and produces a prioritized, *reasoned* action list plus a client-ready report.
|
|
13
|
+
|
|
14
|
+
The data layer is a commodity. What `kvseo` adds is the layer the others skip:
|
|
15
|
+
**synthesis, AI-assisted prioritization, and agency-shaped reporting** — as a
|
|
16
|
+
fast, local, single-command CLI.
|
|
17
|
+
|
|
18
|
+
> **Status: v0.1 feature-complete.** The full pipeline (audit → advise → report)
|
|
19
|
+
> runs end-to-end and is code-reviewed, type-checked, and tested throughout. Not
|
|
20
|
+
> yet on PyPI — install from source today (see [Install](#install)); the tagged
|
|
21
|
+
> release is imminent.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Quickstart
|
|
26
|
+
|
|
27
|
+
Once installed (see [Install](#install)):
|
|
28
|
+
|
|
29
|
+
```console
|
|
30
|
+
$ kvseo init # create config + a local SQLite database
|
|
31
|
+
$ kvseo audit https://example.com # on-page audit + Core Web Vitals → HTML report
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
That loop needs **no API keys and no accounts**. `audit` writes a self-contained
|
|
35
|
+
HTML report to the current directory and prints the findings to your terminal.
|
|
36
|
+
Add data sources and an LLM to go deeper:
|
|
37
|
+
|
|
38
|
+
```console
|
|
39
|
+
$ kvseo connect psi --api-key <key> # PageSpeed Insights (free; raises CWV limits)
|
|
40
|
+
$ kvseo connect csv export.csv --site https://example.com/ # import a GSC CSV export — no OAuth
|
|
41
|
+
$ kvseo connect gsc # …or connect Search Console via OAuth
|
|
42
|
+
$ export ANTHROPIC_API_KEY=… # bring your own LLM key (any LiteLLM provider)
|
|
43
|
+
$ kvseo audit https://example.com # now with GSC context + an AI action list
|
|
44
|
+
$ kvseo cost # what the advisor spent, by provider / model
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## What it does
|
|
48
|
+
|
|
49
|
+
- **On-page audit** — 19 checks across title, meta, headings, content, schema,
|
|
50
|
+
images, and internal links, rolled into a single 0–100 score.
|
|
51
|
+
- **Core Web Vitals** — CrUX field data + Lighthouse lab metrics via the free
|
|
52
|
+
PageSpeed Insights API (works keyless at lower rate limits).
|
|
53
|
+
- **Search Console context** — real query / click / impression / position data
|
|
54
|
+
per URL, via OAuth **or** a plain CSV export (the no-API escape hatch).
|
|
55
|
+
- **AI advisor** — a prioritized "what to do next" list where every
|
|
56
|
+
recommendation is traceable to a specific finding. **Bring your own LLM key**
|
|
57
|
+
(Anthropic, OpenAI, Gemini, or a local Ollama — anything LiteLLM speaks to).
|
|
58
|
+
kvseo never proxies your key or marks up tokens, and `kvseo cost` shows exactly
|
|
59
|
+
what each run spent.
|
|
60
|
+
- **Reports** — a self-contained, single-file HTML report (inline CSS, embedded
|
|
61
|
+
data, zero external requests, print-to-PDF friendly) plus clean Markdown.
|
|
62
|
+
PDF/DOCX land in v0.2.
|
|
63
|
+
|
|
64
|
+
Everything **degrades gracefully**: the audit and HTML report work with no LLM
|
|
65
|
+
key at all — the advisor is an optional layer you unlock by adding a provider
|
|
66
|
+
key. No PSI key → the Core Web Vitals checks skip; no Search Console → the audit
|
|
67
|
+
still scores the page.
|
|
68
|
+
|
|
69
|
+
## Principles
|
|
70
|
+
|
|
71
|
+
- **BYO-key.** kvseo never ships, proxies, or marks up an LLM key.
|
|
72
|
+
- **Local-first.** Everything lives in one SQLite file on your machine — no
|
|
73
|
+
account, no cloud.
|
|
74
|
+
- **Read-only.** kvseo pulls from APIs you authorize; it never writes upstream.
|
|
75
|
+
- **No telemetry.** No phone-home, no usage metrics — not unless you opt in.
|
|
76
|
+
- **Reproducible.** Every audit and advisor run is stored and re-runnable; the
|
|
77
|
+
advisor works from stored data, so you can re-prioritize without re-fetching.
|
|
78
|
+
|
|
79
|
+
## Install
|
|
80
|
+
|
|
81
|
+
Requires **Python 3.12+**.
|
|
82
|
+
|
|
83
|
+
```console
|
|
84
|
+
# from PyPI (once published):
|
|
85
|
+
pipx install kvseo
|
|
86
|
+
|
|
87
|
+
# from source (works today):
|
|
88
|
+
git clone https://github.com/kVadrum/kvseo && cd kvseo
|
|
89
|
+
python3.12 -m venv .venv && . .venv/bin/activate
|
|
90
|
+
pip install -e ".[dev]"
|
|
91
|
+
kvseo --version
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Config lives in `~/.config/kvseo/` and data in `~/.local/share/kvseo/` (XDG;
|
|
95
|
+
`%APPDATA%` / `%LOCALAPPDATA%` on Windows). Override either with the
|
|
96
|
+
`KVSEO_CONFIG_DIR` / `KVSEO_DATA_DIR` environment variables.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
[MIT](./LICENSE) — free to use, modify, and distribute. KeMeK Network © 2026.
|
|
101
|
+
|
|
102
|
+
### Trademarks
|
|
103
|
+
|
|
104
|
+
The "kvseo" and "kVadrum" names are trademarks of KeMeK Network. They are not
|
|
105
|
+
covered by the MIT license, and no rights to use the names are granted by this
|
|
106
|
+
repository. Independent forks must replace the brand name with their own.
|
|
107
|
+
|
|
108
|
+
A [kVadrum](https://kvadrum.com) Lab project, built in public.
|
kvseo-0.6.6/alembic.ini
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Alembic config for in-repo development (dev `alembic` CLI invocations).
|
|
2
|
+
# Runtime migrations are driven programmatically via kvseo.storage.db.migrate,
|
|
3
|
+
# which builds its own Config — this file is for `alembic revision` /
|
|
4
|
+
# `alembic upgrade` / `alembic check` from the repo root.
|
|
5
|
+
|
|
6
|
+
[alembic]
|
|
7
|
+
script_location = src/kvseo/storage/migrations
|
|
8
|
+
file_template = %%(rev)s_%%(slug)s
|
|
9
|
+
prepend_sys_path = src
|
|
10
|
+
sqlalchemy.url = sqlite:///./kvseo-dev.db
|
|
11
|
+
|
|
12
|
+
[loggers]
|
|
13
|
+
keys = root,sqlalchemy,alembic
|
|
14
|
+
|
|
15
|
+
[handlers]
|
|
16
|
+
keys = console
|
|
17
|
+
|
|
18
|
+
[formatters]
|
|
19
|
+
keys = generic
|
|
20
|
+
|
|
21
|
+
[logger_root]
|
|
22
|
+
level = WARNING
|
|
23
|
+
handlers = console
|
|
24
|
+
qualname =
|
|
25
|
+
|
|
26
|
+
[logger_sqlalchemy]
|
|
27
|
+
level = WARNING
|
|
28
|
+
handlers =
|
|
29
|
+
qualname = sqlalchemy.engine
|
|
30
|
+
|
|
31
|
+
[logger_alembic]
|
|
32
|
+
level = INFO
|
|
33
|
+
handlers =
|
|
34
|
+
qualname = alembic
|
|
35
|
+
|
|
36
|
+
[handler_console]
|
|
37
|
+
class = StreamHandler
|
|
38
|
+
args = (sys.stderr,)
|
|
39
|
+
level = NOTSET
|
|
40
|
+
formatter = generic
|
|
41
|
+
|
|
42
|
+
[formatter_generic]
|
|
43
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
44
|
+
datefmt = %H:%M:%S
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kvseo"
|
|
7
|
+
description = "An AI-native SEO copilot for solo operators and small agencies — the AI layer over your existing SEO stack."
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "kVadrum" }]
|
|
13
|
+
keywords = ["seo", "audit", "llm", "ai", "cli", "search-console", "core-web-vitals"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Topic :: Internet :: WWW/HTTP :: Site Management",
|
|
22
|
+
]
|
|
23
|
+
# Canonical version literal — /bump and bump-audit.sh target this field.
|
|
24
|
+
version = "0.6.6"
|
|
25
|
+
|
|
26
|
+
# Runtime dependencies. The advisor (LiteLLM) is core IP per ADR-002; storage
|
|
27
|
+
# is SQLAlchemy + Alembic per ADR-003. Bounds are deliberately loose for the
|
|
28
|
+
# scaffold; LiteLLM gets a minor-pin once a snapshot baseline exists (ADR-002,
|
|
29
|
+
# risk R7).
|
|
30
|
+
dependencies = [
|
|
31
|
+
"typer>=0.12",
|
|
32
|
+
"httpx>=0.27",
|
|
33
|
+
"pydantic>=2.7",
|
|
34
|
+
"pydantic-settings>=2.3",
|
|
35
|
+
"sqlalchemy>=2.0",
|
|
36
|
+
"alembic>=1.13",
|
|
37
|
+
"litellm>=1.40,<2",
|
|
38
|
+
"jinja2>=3.1",
|
|
39
|
+
"markdown>=3.6",
|
|
40
|
+
"selectolax>=0.3.21",
|
|
41
|
+
"platformdirs>=4.2",
|
|
42
|
+
"keyring>=25",
|
|
43
|
+
"google-auth>=2.29",
|
|
44
|
+
"google-auth-oauthlib>=1.2",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.optional-dependencies]
|
|
48
|
+
# PDF rendering lands in v0.2 (ADR-005); declared here so the extra name is
|
|
49
|
+
# stable from day one. Not installed by default.
|
|
50
|
+
pdf = ["playwright>=1.44"]
|
|
51
|
+
dev = [
|
|
52
|
+
"pytest>=8",
|
|
53
|
+
"pytest-asyncio>=0.23",
|
|
54
|
+
"ruff>=0.5",
|
|
55
|
+
"mypy>=1.10",
|
|
56
|
+
"types-Markdown",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[project.scripts]
|
|
60
|
+
kvseo = "kvseo.cli:app"
|
|
61
|
+
|
|
62
|
+
[project.urls]
|
|
63
|
+
Homepage = "https://github.com/kvadrum/kvseo"
|
|
64
|
+
Repository = "https://github.com/kvadrum/kvseo"
|
|
65
|
+
|
|
66
|
+
[tool.hatch.build.targets.wheel]
|
|
67
|
+
packages = ["src/kvseo"]
|
|
68
|
+
|
|
69
|
+
[tool.ruff]
|
|
70
|
+
line-length = 120 # settled width — fits a typed mapped_column or a message-bearing check return on one line
|
|
71
|
+
target-version = "py312"
|
|
72
|
+
src = ["src", "tests"]
|
|
73
|
+
# Alembic migration scripts are generated and follow Alembic's own style.
|
|
74
|
+
extend-exclude = ["src/kvseo/storage/migrations"]
|
|
75
|
+
|
|
76
|
+
[tool.ruff.lint]
|
|
77
|
+
select = ["E", "F", "I", "UP", "B", "SIM", "C4", "PTH", "RUF"]
|
|
78
|
+
|
|
79
|
+
[tool.mypy]
|
|
80
|
+
python_version = "3.12"
|
|
81
|
+
strict = true
|
|
82
|
+
files = ["src/kvseo"]
|
|
83
|
+
# Alembic env/versions use dynamic context objects; not worth strict-typing.
|
|
84
|
+
exclude = ["^src/kvseo/storage/migrations/"]
|
|
85
|
+
# These ship partial or no type stubs; don't fail strict on them.
|
|
86
|
+
[[tool.mypy.overrides]]
|
|
87
|
+
module = ["selectolax.*", "litellm.*", "google.*", "google_auth_oauthlib.*"]
|
|
88
|
+
ignore_missing_imports = true
|
|
89
|
+
|
|
90
|
+
[tool.pytest.ini_options]
|
|
91
|
+
testpaths = ["tests"]
|
|
92
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""kvseo — an AI-native SEO copilot for solo operators and small agencies.
|
|
2
|
+
|
|
3
|
+
The canonical version is the ``version`` field in ``pyproject.toml`` (what
|
|
4
|
+
/bump and bump-audit edit). ``__version__`` resolves it from installed package
|
|
5
|
+
metadata at runtime; the literal below is only a fallback for running against
|
|
6
|
+
an uninstalled source tree.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
__version__ = version("kvseo")
|
|
15
|
+
except PackageNotFoundError: # not installed (e.g. running from a source tree)
|
|
16
|
+
__version__ = "0.0.0"
|
|
17
|
+
|
|
18
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""kvseo command-line interface (Typer).
|
|
2
|
+
|
|
3
|
+
This module is the only entry point in v0.1. It is a thin shell over
|
|
4
|
+
``kvseo.core``; all business logic lives below the CLI (see 02-architecture.md
|
|
5
|
+
§2 for the layering rule).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
|
|
14
|
+
from kvseo import __version__
|
|
15
|
+
from kvseo.cli import advisor, audit, config, connect, cost, init, report
|
|
16
|
+
|
|
17
|
+
app = typer.Typer(
|
|
18
|
+
name="kvseo",
|
|
19
|
+
help="AI-native SEO copilot for solo operators and small agencies.",
|
|
20
|
+
no_args_is_help=True,
|
|
21
|
+
add_completion=False,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _version_callback(value: bool) -> None:
|
|
26
|
+
if value:
|
|
27
|
+
typer.echo(f"kvseo {__version__}")
|
|
28
|
+
raise typer.Exit()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.callback()
|
|
32
|
+
def main(
|
|
33
|
+
version: Annotated[
|
|
34
|
+
bool,
|
|
35
|
+
typer.Option(
|
|
36
|
+
"--version",
|
|
37
|
+
"-V",
|
|
38
|
+
help="Show the kvseo version and exit.",
|
|
39
|
+
is_eager=True,
|
|
40
|
+
callback=_version_callback,
|
|
41
|
+
),
|
|
42
|
+
] = False,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""kvseo — the AI layer over your existing SEO stack."""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# --- Command wiring -------------------------------------------------------
|
|
48
|
+
# Top-level verbs.
|
|
49
|
+
app.command()(init.init)
|
|
50
|
+
app.command()(audit.audit)
|
|
51
|
+
app.command()(report.report)
|
|
52
|
+
app.command()(cost.cost)
|
|
53
|
+
# Sub-command groups.
|
|
54
|
+
app.add_typer(connect.app, name="connect")
|
|
55
|
+
app.add_typer(config.app, name="config")
|
|
56
|
+
app.add_typer(advisor.app, name="advisor")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Shared CLI helpers used across kvseo command modules.
|
|
2
|
+
|
|
3
|
+
Small building blocks the command modules reach for — an error-exit helper and
|
|
4
|
+
the audit-ID parser — kept here so their message + exit-code contract has one
|
|
5
|
+
home instead of a copy per command.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import uuid
|
|
11
|
+
from typing import NoReturn
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def fail(message: str, *, code: int) -> NoReturn:
|
|
17
|
+
"""Print an error line to stderr (red) and exit with ``code``."""
|
|
18
|
+
typer.secho(message, fg=typer.colors.RED, err=True)
|
|
19
|
+
raise typer.Exit(code=code)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_audit_id(value: str) -> uuid.UUID:
|
|
23
|
+
"""Parse a CLI-supplied audit ID, or exit 2 with a clear message."""
|
|
24
|
+
try:
|
|
25
|
+
return uuid.UUID(value)
|
|
26
|
+
except ValueError:
|
|
27
|
+
fail(f"'{value}' is not a valid audit ID.", code=2)
|