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.
Files changed (93) hide show
  1. kvseo-0.6.6/.github/workflows/ci.yml +32 -0
  2. kvseo-0.6.6/.github/workflows/release.yml +46 -0
  3. kvseo-0.6.6/.gitignore +42 -0
  4. kvseo-0.6.6/LICENSE +21 -0
  5. kvseo-0.6.6/PKG-INFO +150 -0
  6. kvseo-0.6.6/README.md +108 -0
  7. kvseo-0.6.6/alembic.ini +44 -0
  8. kvseo-0.6.6/pyproject.toml +92 -0
  9. kvseo-0.6.6/src/kvseo/__init__.py +18 -0
  10. kvseo-0.6.6/src/kvseo/__main__.py +8 -0
  11. kvseo-0.6.6/src/kvseo/cli/__init__.py +56 -0
  12. kvseo-0.6.6/src/kvseo/cli/_util.py +27 -0
  13. kvseo-0.6.6/src/kvseo/cli/advisor.py +131 -0
  14. kvseo-0.6.6/src/kvseo/cli/audit.py +163 -0
  15. kvseo-0.6.6/src/kvseo/cli/config.py +18 -0
  16. kvseo-0.6.6/src/kvseo/cli/connect.py +166 -0
  17. kvseo-0.6.6/src/kvseo/cli/cost.py +108 -0
  18. kvseo-0.6.6/src/kvseo/cli/init.py +29 -0
  19. kvseo-0.6.6/src/kvseo/cli/report.py +103 -0
  20. kvseo-0.6.6/src/kvseo/config/__init__.py +1 -0
  21. kvseo-0.6.6/src/kvseo/config/paths.py +43 -0
  22. kvseo-0.6.6/src/kvseo/config/secrets.py +60 -0
  23. kvseo-0.6.6/src/kvseo/config/settings.py +75 -0
  24. kvseo-0.6.6/src/kvseo/connectors/__init__.py +5 -0
  25. kvseo-0.6.6/src/kvseo/connectors/base.py +52 -0
  26. kvseo-0.6.6/src/kvseo/connectors/csv.py +273 -0
  27. kvseo-0.6.6/src/kvseo/connectors/gsc.py +306 -0
  28. kvseo-0.6.6/src/kvseo/connectors/gsc_auth.py +96 -0
  29. kvseo-0.6.6/src/kvseo/connectors/psi.py +247 -0
  30. kvseo-0.6.6/src/kvseo/core/__init__.py +5 -0
  31. kvseo-0.6.6/src/kvseo/core/advisor/__init__.py +1 -0
  32. kvseo-0.6.6/src/kvseo/core/advisor/client.py +369 -0
  33. kvseo-0.6.6/src/kvseo/core/advisor/context.py +164 -0
  34. kvseo-0.6.6/src/kvseo/core/advisor/prompts.py +99 -0
  35. kvseo-0.6.6/src/kvseo/core/advisor/schemas.py +73 -0
  36. kvseo-0.6.6/src/kvseo/core/audit/__init__.py +1 -0
  37. kvseo-0.6.6/src/kvseo/core/audit/checks/__init__.py +24 -0
  38. kvseo-0.6.6/src/kvseo/core/audit/checks/_base.py +37 -0
  39. kvseo-0.6.6/src/kvseo/core/audit/checks/content.py +75 -0
  40. kvseo-0.6.6/src/kvseo/core/audit/checks/cwv.py +66 -0
  41. kvseo-0.6.6/src/kvseo/core/audit/checks/headings.py +34 -0
  42. kvseo-0.6.6/src/kvseo/core/audit/checks/meta.py +69 -0
  43. kvseo-0.6.6/src/kvseo/core/audit/checks/title.py +40 -0
  44. kvseo-0.6.6/src/kvseo/core/audit/document.py +139 -0
  45. kvseo-0.6.6/src/kvseo/core/audit/engine.py +198 -0
  46. kvseo-0.6.6/src/kvseo/core/audit/fetcher.py +94 -0
  47. kvseo-0.6.6/src/kvseo/core/audit/scoring.py +31 -0
  48. kvseo-0.6.6/src/kvseo/core/cost.py +105 -0
  49. kvseo-0.6.6/src/kvseo/core/report/__init__.py +1 -0
  50. kvseo-0.6.6/src/kvseo/core/report/renderer.py +193 -0
  51. kvseo-0.6.6/src/kvseo/core/report/templates/report.html.j2 +305 -0
  52. kvseo-0.6.6/src/kvseo/core/report/templates/report.md.j2 +96 -0
  53. kvseo-0.6.6/src/kvseo/py.typed +0 -0
  54. kvseo-0.6.6/src/kvseo/storage/__init__.py +1 -0
  55. kvseo-0.6.6/src/kvseo/storage/db.py +76 -0
  56. kvseo-0.6.6/src/kvseo/storage/migrations/env.py +57 -0
  57. kvseo-0.6.6/src/kvseo/storage/migrations/script.py.mako +26 -0
  58. kvseo-0.6.6/src/kvseo/storage/migrations/versions/0001_initial_schema.py +212 -0
  59. kvseo-0.6.6/src/kvseo/storage/models.py +256 -0
  60. kvseo-0.6.6/src/kvseo/storage/timestamps.py +24 -0
  61. kvseo-0.6.6/src/kvseo/storage/types.py +39 -0
  62. kvseo-0.6.6/tests/advisor/test_client.py +244 -0
  63. kvseo-0.6.6/tests/advisor/test_context.py +69 -0
  64. kvseo-0.6.6/tests/advisor/test_schemas.py +63 -0
  65. kvseo-0.6.6/tests/audit/test_engine.py +114 -0
  66. kvseo-0.6.6/tests/audit/test_fetcher.py +34 -0
  67. kvseo-0.6.6/tests/checks/test_checks.py +110 -0
  68. kvseo-0.6.6/tests/conftest.py +133 -0
  69. kvseo-0.6.6/tests/connectors/test_base.py +29 -0
  70. kvseo-0.6.6/tests/connectors/test_connect.py +81 -0
  71. kvseo-0.6.6/tests/connectors/test_csv.py +166 -0
  72. kvseo-0.6.6/tests/connectors/test_gsc.py +171 -0
  73. kvseo-0.6.6/tests/connectors/test_gsc_auth.py +23 -0
  74. kvseo-0.6.6/tests/connectors/test_psi.py +140 -0
  75. kvseo-0.6.6/tests/cost/test_cost.py +147 -0
  76. kvseo-0.6.6/tests/fixtures/csv/queries_bom_nopage.csv +3 -0
  77. kvseo-0.6.6/tests/fixtures/csv/queries_happy.csv +4 -0
  78. kvseo-0.6.6/tests/fixtures/csv/queries_mismatch.csv +3 -0
  79. kvseo-0.6.6/tests/fixtures/csv/queries_mostly_bad.csv +5 -0
  80. kvseo-0.6.6/tests/fixtures/csv/queries_outofrange.csv +6 -0
  81. kvseo-0.6.6/tests/fixtures/csv/queries_partial.csv +5 -0
  82. kvseo-0.6.6/tests/fixtures/csv/queries_renamed.csv +3 -0
  83. kvseo-0.6.6/tests/fixtures/html/good.html +26 -0
  84. kvseo-0.6.6/tests/fixtures/html/poor.html +14 -0
  85. kvseo-0.6.6/tests/fixtures/psi/example_mobile.json +36 -0
  86. kvseo-0.6.6/tests/report/test_renderer.py +103 -0
  87. kvseo-0.6.6/tests/storage/test_migrations.py +80 -0
  88. kvseo-0.6.6/tests/storage/test_models.py +92 -0
  89. kvseo-0.6.6/tests/test_cli.py +22 -0
  90. kvseo-0.6.6/tests/test_config_paths.py +25 -0
  91. kvseo-0.6.6/tests/test_init.py +52 -0
  92. kvseo-0.6.6/tests/test_secrets.py +46 -0
  93. 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
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/)
48
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
49
+ [![Built in public](https://img.shields.io/badge/built%20in%20public-kVadrum-e2725b.svg)](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
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
7
+ [![Built in public](https://img.shields.io/badge/built%20in%20public-kVadrum-e2725b.svg)](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.
@@ -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,8 @@
1
+ """Enable ``python -m kvseo`` as an alias for the ``kvseo`` console script."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from kvseo.cli import app
6
+
7
+ if __name__ == "__main__":
8
+ app()
@@ -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)