buysmarter 0.1.2__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.
- buysmarter-0.1.2/.env.example +19 -0
- buysmarter-0.1.2/.github/ISSUE_TEMPLATE/bug.yml +28 -0
- buysmarter-0.1.2/.github/ISSUE_TEMPLATE/config.yml +5 -0
- buysmarter-0.1.2/.github/ISSUE_TEMPLATE/feature.yml +10 -0
- buysmarter-0.1.2/.github/dependabot.yml +14 -0
- buysmarter-0.1.2/.github/pull_request_template.md +6 -0
- buysmarter-0.1.2/.github/workflows/ci.yml +34 -0
- buysmarter-0.1.2/.github/workflows/install.yml +36 -0
- buysmarter-0.1.2/.github/workflows/release.yml +79 -0
- buysmarter-0.1.2/.github/workflows/scorecard.yml +30 -0
- buysmarter-0.1.2/.github/workflows/security.yml +36 -0
- buysmarter-0.1.2/.gitignore +28 -0
- buysmarter-0.1.2/.gitleaks.toml +6 -0
- buysmarter-0.1.2/.mcp.json +13 -0
- buysmarter-0.1.2/.pre-commit-config.yaml +13 -0
- buysmarter-0.1.2/.python-version +1 -0
- buysmarter-0.1.2/CHANGELOG.md +41 -0
- buysmarter-0.1.2/CODE_OF_CONDUCT.md +6 -0
- buysmarter-0.1.2/CODE_OF_CONDUCT.ru.md +6 -0
- buysmarter-0.1.2/CONTRIBUTING.md +26 -0
- buysmarter-0.1.2/CONTRIBUTING.ru.md +26 -0
- buysmarter-0.1.2/LICENSE +21 -0
- buysmarter-0.1.2/Makefile +15 -0
- buysmarter-0.1.2/PKG-INFO +238 -0
- buysmarter-0.1.2/README.md +211 -0
- buysmarter-0.1.2/README.ru.md +213 -0
- buysmarter-0.1.2/SECURITY.md +9 -0
- buysmarter-0.1.2/SECURITY.ru.md +9 -0
- buysmarter-0.1.2/THIRD_PARTY_NOTICES.md +32 -0
- buysmarter-0.1.2/alembic/README +1 -0
- buysmarter-0.1.2/alembic/env.py +94 -0
- buysmarter-0.1.2/alembic/script.py.mako +28 -0
- buysmarter-0.1.2/alembic/versions/05dbcfa0e44d_add_prompt_text_to_llm_cache.py +32 -0
- buysmarter-0.1.2/alembic/versions/3c9f1a2b7d4e_add_review_stats_to_price_snapshots.py +30 -0
- buysmarter-0.1.2/alembic/versions/8e237249c427_initial_schema.py +122 -0
- buysmarter-0.1.2/alembic.ini +150 -0
- buysmarter-0.1.2/assets/readme/hero.ru.svg +52 -0
- buysmarter-0.1.2/assets/readme/hero.svg +52 -0
- buysmarter-0.1.2/assets/readme/social-preview.html +6 -0
- buysmarter-0.1.2/assets/readme/social-preview.png +0 -0
- buysmarter-0.1.2/docker-compose.yml +32 -0
- buysmarter-0.1.2/docs/analogs/README.md +60 -0
- buysmarter-0.1.2/docs/analogs/kaspi-mcp-free_kaspipulse.md +58 -0
- buysmarter-0.1.2/docs/analogs/kaspi-skill.md +50 -0
- buysmarter-0.1.2/docs/analogs/shop-scout_ozon-mcp.md +73 -0
- buysmarter-0.1.2/docs/analogs/smart-shopper.md +40 -0
- buysmarter-0.1.2/docs/analogs/tls-impersonation-olx.md +28 -0
- buysmarter-0.1.2/docs/spikes/kaspi-spike.md +53 -0
- buysmarter-0.1.2/docs/spikes/olx-spike.md +109 -0
- buysmarter-0.1.2/docs/spikes/ozon-spike.md +101 -0
- buysmarter-0.1.2/install.ps1 +80 -0
- buysmarter-0.1.2/install.sh +87 -0
- buysmarter-0.1.2/pyproject.toml +67 -0
- buysmarter-0.1.2/src/buysmarter/__init__.py +3 -0
- buysmarter-0.1.2/src/buysmarter/api/__init__.py +0 -0
- buysmarter-0.1.2/src/buysmarter/api/main.py +11 -0
- buysmarter-0.1.2/src/buysmarter/cli/__init__.py +0 -0
- buysmarter-0.1.2/src/buysmarter/cli/main.py +655 -0
- buysmarter-0.1.2/src/buysmarter/cli/report.py +259 -0
- buysmarter-0.1.2/src/buysmarter/config.py +49 -0
- buysmarter-0.1.2/src/buysmarter/db/__init__.py +0 -0
- buysmarter-0.1.2/src/buysmarter/db/base.py +39 -0
- buysmarter-0.1.2/src/buysmarter/db/models.py +123 -0
- buysmarter-0.1.2/src/buysmarter/db/repo.py +268 -0
- buysmarter-0.1.2/src/buysmarter/knowledge/__init__.py +9 -0
- buysmarter-0.1.2/src/buysmarter/knowledge/brands.py +247 -0
- buysmarter-0.1.2/src/buysmarter/knowledge/generate.py +205 -0
- buysmarter-0.1.2/src/buysmarter/knowledge/ingredients.py +131 -0
- buysmarter-0.1.2/src/buysmarter/knowledge/models_book.py +145 -0
- buysmarter-0.1.2/src/buysmarter/knowledge/store.py +341 -0
- buysmarter-0.1.2/src/buysmarter/llm/__init__.py +5 -0
- buysmarter-0.1.2/src/buysmarter/llm/adapter.py +448 -0
- buysmarter-0.1.2/src/buysmarter/llm/cache.py +188 -0
- buysmarter-0.1.2/src/buysmarter/llm/canonicalize.py +173 -0
- buysmarter-0.1.2/src/buysmarter/llm/ingredients.py +182 -0
- buysmarter-0.1.2/src/buysmarter/llm/jev.py +68 -0
- buysmarter-0.1.2/src/buysmarter/llm/models.py +35 -0
- buysmarter-0.1.2/src/buysmarter/llm/prompts.py +127 -0
- buysmarter-0.1.2/src/buysmarter/llm/relevance.py +192 -0
- buysmarter-0.1.2/src/buysmarter/llm/review_summary.py +383 -0
- buysmarter-0.1.2/src/buysmarter/logsetup.py +92 -0
- buysmarter-0.1.2/src/buysmarter/mcp_server/__init__.py +0 -0
- buysmarter-0.1.2/src/buysmarter/mcp_server/__main__.py +9 -0
- buysmarter-0.1.2/src/buysmarter/mcp_server/server.py +259 -0
- buysmarter-0.1.2/src/buysmarter/memory/__init__.py +0 -0
- buysmarter-0.1.2/src/buysmarter/memory/prefs.py +81 -0
- buysmarter-0.1.2/src/buysmarter/normalize/__init__.py +0 -0
- buysmarter-0.1.2/src/buysmarter/normalize/candidate.py +119 -0
- buysmarter-0.1.2/src/buysmarter/ranking/__init__.py +0 -0
- buysmarter-0.1.2/src/buysmarter/ranking/delivery.py +91 -0
- buysmarter-0.1.2/src/buysmarter/ranking/engine.py +1317 -0
- buysmarter-0.1.2/src/buysmarter/ranking/model_tokens.py +48 -0
- buysmarter-0.1.2/src/buysmarter/ranking/models.py +160 -0
- buysmarter-0.1.2/src/buysmarter/ranking/review_requirements.py +62 -0
- buysmarter-0.1.2/src/buysmarter/ranking/review_trust.py +311 -0
- buysmarter-0.1.2/src/buysmarter/ranking/spec_conflicts.py +76 -0
- buysmarter-0.1.2/src/buysmarter/ranking/used_defects.py +55 -0
- buysmarter-0.1.2/src/buysmarter/ranking/velocity.py +42 -0
- buysmarter-0.1.2/src/buysmarter/services/__init__.py +0 -0
- buysmarter-0.1.2/src/buysmarter/services/phone_specs.py +202 -0
- buysmarter-0.1.2/src/buysmarter/services/recommend.py +2107 -0
- buysmarter-0.1.2/tests/__init__.py +0 -0
- buysmarter-0.1.2/tests/cli/test_doctor.py +28 -0
- buysmarter-0.1.2/tests/cli/test_learn.py +145 -0
- buysmarter-0.1.2/tests/cli/test_main.py +345 -0
- buysmarter-0.1.2/tests/cli/test_report.py +224 -0
- buysmarter-0.1.2/tests/conftest.py +89 -0
- buysmarter-0.1.2/tests/db/__init__.py +0 -0
- buysmarter-0.1.2/tests/db/test_price_snapshot_model.py +8 -0
- buysmarter-0.1.2/tests/db/test_repo.py +133 -0
- buysmarter-0.1.2/tests/db/test_tracked_categories.py +23 -0
- buysmarter-0.1.2/tests/knowledge/__init__.py +0 -0
- buysmarter-0.1.2/tests/knowledge/test_brands.py +133 -0
- buysmarter-0.1.2/tests/knowledge/test_generate.py +121 -0
- buysmarter-0.1.2/tests/knowledge/test_ingredients.py +114 -0
- buysmarter-0.1.2/tests/knowledge/test_models_book.py +78 -0
- buysmarter-0.1.2/tests/knowledge/test_store.py +214 -0
- buysmarter-0.1.2/tests/llm/__init__.py +0 -0
- buysmarter-0.1.2/tests/llm/test_adapter.py +380 -0
- buysmarter-0.1.2/tests/llm/test_cache.py +114 -0
- buysmarter-0.1.2/tests/llm/test_canonicalize.py +167 -0
- buysmarter-0.1.2/tests/llm/test_ingredients.py +157 -0
- buysmarter-0.1.2/tests/llm/test_jev.py +41 -0
- buysmarter-0.1.2/tests/llm/test_prompts.py +64 -0
- buysmarter-0.1.2/tests/llm/test_relevance.py +161 -0
- buysmarter-0.1.2/tests/llm/test_review_summary.py +412 -0
- buysmarter-0.1.2/tests/mcp_server/__init__.py +0 -0
- buysmarter-0.1.2/tests/mcp_server/test_tools.py +235 -0
- buysmarter-0.1.2/tests/memory/__init__.py +0 -0
- buysmarter-0.1.2/tests/memory/test_prefs.py +12 -0
- buysmarter-0.1.2/tests/normalize/__init__.py +0 -0
- buysmarter-0.1.2/tests/normalize/test_candidate.py +116 -0
- buysmarter-0.1.2/tests/ranking/__init__.py +0 -0
- buysmarter-0.1.2/tests/ranking/test_delivery.py +132 -0
- buysmarter-0.1.2/tests/ranking/test_engine.py +1358 -0
- buysmarter-0.1.2/tests/ranking/test_ingredient_axis.py +154 -0
- buysmarter-0.1.2/tests/ranking/test_model_relevance.py +96 -0
- buysmarter-0.1.2/tests/ranking/test_review_requirements.py +76 -0
- buysmarter-0.1.2/tests/ranking/test_review_trust.py +270 -0
- buysmarter-0.1.2/tests/ranking/test_spec_conflicts.py +77 -0
- buysmarter-0.1.2/tests/ranking/test_substance_filter.py +135 -0
- buysmarter-0.1.2/tests/ranking/test_used_defects.py +51 -0
- buysmarter-0.1.2/tests/ranking/test_velocity.py +37 -0
- buysmarter-0.1.2/tests/samples/kaspi/offers.json +103 -0
- buysmarter-0.1.2/tests/samples/kaspi/reviews.json +221 -0
- buysmarter-0.1.2/tests/samples/kaspi/search.json +1174 -0
- buysmarter-0.1.2/tests/samples/kaspi/specs.json +355 -0
- buysmarter-0.1.2/tests/samples/olx/detail.json +145 -0
- buysmarter-0.1.2/tests/samples/olx/search.json +636 -0
- buysmarter-0.1.2/tests/samples/ozon/product.json +1605 -0
- buysmarter-0.1.2/tests/samples/ozon/reviews.json +1270 -0
- buysmarter-0.1.2/tests/samples/ozon/search.json +818 -0
- buysmarter-0.1.2/tests/services/__init__.py +0 -0
- buysmarter-0.1.2/tests/services/test_phone_specs.py +138 -0
- buysmarter-0.1.2/tests/services/test_query_constraints.py +25 -0
- buysmarter-0.1.2/tests/services/test_recommend.py +1523 -0
- buysmarter-0.1.2/tests/services/test_recommend_canonicalize.py +154 -0
- buysmarter-0.1.2/tests/services/test_recommend_composition.py +63 -0
- buysmarter-0.1.2/tests/services/test_recommend_concurrency.py +51 -0
- buysmarter-0.1.2/tests/services/test_recommend_logging.py +20 -0
- buysmarter-0.1.2/tests/services/test_recommend_substance.py +199 -0
- buysmarter-0.1.2/tests/services/test_relevance_filter.py +73 -0
- buysmarter-0.1.2/tests/sources/__init__.py +0 -0
- buysmarter-0.1.2/tests/sources/test_kaspi_parse.py +189 -0
- buysmarter-0.1.2/tests/sources/test_olx_parse.py +102 -0
- buysmarter-0.1.2/tests/sources/test_ozon_parse.py +143 -0
- buysmarter-0.1.2/tests/test_config.py +10 -0
- buysmarter-0.1.2/tests/test_logsetup.py +134 -0
- buysmarter-0.1.2/tests/test_smoke.py +23 -0
- buysmarter-0.1.2/uv.lock +2147 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Copy to .env and adjust as needed.
|
|
2
|
+
|
|
3
|
+
DATABASE_URL=postgresql+asyncpg://buysmarter:buysmarter@localhost:5434/buysmarter
|
|
4
|
+
REDIS_URL=redis://localhost:6380/0
|
|
5
|
+
|
|
6
|
+
# LLM: primary provider is the local `claude` CLI through OmniRoute (the model
|
|
7
|
+
# picks the combo; only opus/hard puts the Claude subscription first) — NO API key required. The keys below are an optional API fallback,
|
|
8
|
+
# used only when you set DEFAULT_LLM_MODEL to a non-CLI model that has its key.
|
|
9
|
+
OPENAI_API_KEY=
|
|
10
|
+
ANTHROPIC_API_KEY=
|
|
11
|
+
GEMINI_API_KEY=
|
|
12
|
+
DEEPSEEK_API_KEY=
|
|
13
|
+
|
|
14
|
+
# LLM adapter defaults. haiku/sonnet/opus route to the local `claude` CLI.
|
|
15
|
+
DEFAULT_LLM_MODEL=sonnet
|
|
16
|
+
PROMPT_VERSION=v1
|
|
17
|
+
|
|
18
|
+
# Kaspi.kz source (optional; residential proxy for datacenter IPs Kaspi 429s).
|
|
19
|
+
KASPI_PROXY=
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Something broke or ranked wrong
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: command
|
|
7
|
+
attributes:
|
|
8
|
+
label: Command
|
|
9
|
+
description: The exact command or MCP call
|
|
10
|
+
render: shell
|
|
11
|
+
validations:
|
|
12
|
+
required: true
|
|
13
|
+
- type: textarea
|
|
14
|
+
id: what
|
|
15
|
+
attributes:
|
|
16
|
+
label: What happened and what you expected
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: textarea
|
|
20
|
+
id: doctor
|
|
21
|
+
attributes:
|
|
22
|
+
label: Output of `uv run buysmarter doctor`
|
|
23
|
+
render: text
|
|
24
|
+
- type: textarea
|
|
25
|
+
id: log
|
|
26
|
+
attributes:
|
|
27
|
+
label: Relevant lines from logs/buysmarter.log
|
|
28
|
+
render: text
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
name: Feature or category request
|
|
2
|
+
description: A new source, category or ranking signal
|
|
3
|
+
labels: [enhancement]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: need
|
|
7
|
+
attributes:
|
|
8
|
+
label: What you want to buy or check, and what the report misses
|
|
9
|
+
validations:
|
|
10
|
+
required: true
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: uv
|
|
4
|
+
directory: /
|
|
5
|
+
schedule:
|
|
6
|
+
interval: monthly
|
|
7
|
+
cooldown: { default-days: 7 } # skip releases younger than a week (hijacked-release window)
|
|
8
|
+
- package-ecosystem: github-actions
|
|
9
|
+
directory: /
|
|
10
|
+
schedule:
|
|
11
|
+
interval: monthly
|
|
12
|
+
cooldown: { default-days: 7 }
|
|
13
|
+
groups:
|
|
14
|
+
actions: { patterns: ["*"] }
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
check:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
timeout-minutes: 20
|
|
15
|
+
services:
|
|
16
|
+
postgres:
|
|
17
|
+
image: postgres:16
|
|
18
|
+
env:
|
|
19
|
+
POSTGRES_USER: buysmarter
|
|
20
|
+
POSTGRES_PASSWORD: buysmarter
|
|
21
|
+
POSTGRES_DB: buysmarter
|
|
22
|
+
ports: ["5434:5432"]
|
|
23
|
+
options: >-
|
|
24
|
+
--health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 10
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
27
|
+
with:
|
|
28
|
+
persist-credentials: false
|
|
29
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
30
|
+
- run: uv sync --locked
|
|
31
|
+
- run: uv run alembic upgrade head
|
|
32
|
+
- run: uv run ruff check .
|
|
33
|
+
- run: uv run ruff format --check .
|
|
34
|
+
- run: uv run pytest -q
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: install
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
paths: [install.sh, install.ps1, pyproject.toml, uv.lock, "src/**", .github/workflows/install.yml]
|
|
6
|
+
pull_request:
|
|
7
|
+
paths: [install.sh, install.ps1, pyproject.toml, uv.lock, "src/**", .github/workflows/install.yml]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
jobs:
|
|
12
|
+
install:
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
17
|
+
runs-on: ${{ matrix.os }}
|
|
18
|
+
timeout-minutes: 20
|
|
19
|
+
env:
|
|
20
|
+
INSTALL_SOURCE: ${{ github.workspace }} # install this commit, not what is on GitHub
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
23
|
+
with:
|
|
24
|
+
persist-credentials: false
|
|
25
|
+
- if: runner.os != 'Windows'
|
|
26
|
+
run: |
|
|
27
|
+
sh install.sh
|
|
28
|
+
~/.local/bin/buysmarter --help
|
|
29
|
+
~/.local/bin/buysmarter doctor
|
|
30
|
+
- if: runner.os == 'Windows'
|
|
31
|
+
shell: powershell # Windows PowerShell 5.1, what the README one-liner runs
|
|
32
|
+
run: |
|
|
33
|
+
./install.ps1
|
|
34
|
+
$bin = Join-Path (uv tool dir --bin) "buysmarter.exe"
|
|
35
|
+
& $bin --help; if ($LASTEXITCODE -ne 0) { exit 1 }
|
|
36
|
+
& $bin doctor; if ($LASTEXITCODE -ne 0) { exit 1 }
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Tag vX.Y.Z → tests → wheel and sdist → PyPI (trusted publishing) → GitHub release with build provenance; install.sh/install.ps1 install that wheel.
|
|
2
|
+
name: release
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
branches: [main]
|
|
7
|
+
paths: [.github/workflows/release.yml]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
jobs:
|
|
12
|
+
check:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
timeout-minutes: 15
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
17
|
+
with:
|
|
18
|
+
persist-credentials: false
|
|
19
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
20
|
+
with:
|
|
21
|
+
enable-cache: false # no cache in a release workflow: a poisoned cache would ship in the artifacts
|
|
22
|
+
- name: tag must match the pyproject version (the installers derive the wheel name from the tag)
|
|
23
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
24
|
+
run: test "v$(uv version --short)" = "$GITHUB_REF_NAME"
|
|
25
|
+
- run: uv sync --locked && uv run pytest -q
|
|
26
|
+
|
|
27
|
+
build:
|
|
28
|
+
needs: check
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
timeout-minutes: 15
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
33
|
+
with:
|
|
34
|
+
persist-credentials: false
|
|
35
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
36
|
+
with:
|
|
37
|
+
enable-cache: false
|
|
38
|
+
- run: uv build
|
|
39
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
40
|
+
with:
|
|
41
|
+
name: dist
|
|
42
|
+
path: dist/
|
|
43
|
+
|
|
44
|
+
pypi:
|
|
45
|
+
needs: build
|
|
46
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
timeout-minutes: 15
|
|
49
|
+
environment: pypi
|
|
50
|
+
permissions:
|
|
51
|
+
id-token: write
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
54
|
+
with:
|
|
55
|
+
name: dist
|
|
56
|
+
path: dist/
|
|
57
|
+
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
58
|
+
|
|
59
|
+
github-release:
|
|
60
|
+
needs: [build, pypi]
|
|
61
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
timeout-minutes: 15
|
|
64
|
+
permissions:
|
|
65
|
+
contents: write # create the release
|
|
66
|
+
id-token: write # build provenance
|
|
67
|
+
attestations: write
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
70
|
+
with:
|
|
71
|
+
path: dist/
|
|
72
|
+
merge-multiple: true
|
|
73
|
+
- uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
|
74
|
+
with:
|
|
75
|
+
subject-path: dist/*
|
|
76
|
+
- env:
|
|
77
|
+
GH_TOKEN: ${{ github.token }}
|
|
78
|
+
TAG: ${{ github.ref_name }}
|
|
79
|
+
run: gh release create "$TAG" dist/* --repo "$GITHUB_REPOSITORY" --generate-notes --verify-tag
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Copy to <repo>/.github/workflows/scorecard.yml (public repos). Results land in Security → Code scanning
|
|
2
|
+
# and on https://scorecard.dev; badge for the README:
|
|
3
|
+
# [](https://scorecard.dev/viewer/?uri=github.com/octrow/buy-smarter)
|
|
4
|
+
# scorecard-action rejects workflows with top-level env/defaults or extra write permissions; keep this shape.
|
|
5
|
+
name: scorecard
|
|
6
|
+
on:
|
|
7
|
+
branch_protection_rule:
|
|
8
|
+
push:
|
|
9
|
+
branches: [main]
|
|
10
|
+
schedule:
|
|
11
|
+
- cron: "29 4 * * 1"
|
|
12
|
+
permissions: read-all
|
|
13
|
+
jobs:
|
|
14
|
+
analysis:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
permissions:
|
|
17
|
+
security-events: write # upload SARIF
|
|
18
|
+
id-token: write # publish_results
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
21
|
+
with:
|
|
22
|
+
persist-credentials: false
|
|
23
|
+
- uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0
|
|
24
|
+
with:
|
|
25
|
+
results_file: results.sarif
|
|
26
|
+
results_format: sarif
|
|
27
|
+
publish_results: true
|
|
28
|
+
- uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
|
29
|
+
with:
|
|
30
|
+
sarif_file: results.sarif
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: security
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
pull_request:
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: "17 3 * * 1"
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
jobs:
|
|
14
|
+
gitleaks:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
timeout-minutes: 10
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
pull-requests: write # PR comments on a finding; drop it with GITLEAKS_ENABLE_COMMENTS=false
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
persist-credentials: false
|
|
25
|
+
- uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e # v3.0.0
|
|
26
|
+
env:
|
|
27
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
28
|
+
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
|
|
29
|
+
zizmor:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
timeout-minutes: 10
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
34
|
+
with:
|
|
35
|
+
persist-credentials: false
|
|
36
|
+
- run: pipx run zizmor==1.30.1 --min-severity medium .github/workflows
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
|
|
9
|
+
# Virtual env
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
|
|
13
|
+
# Env / secrets
|
|
14
|
+
.env
|
|
15
|
+
.env.*
|
|
16
|
+
!.env.example
|
|
17
|
+
|
|
18
|
+
# Tooling caches
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.ruff_cache/
|
|
21
|
+
.mypy_cache/
|
|
22
|
+
|
|
23
|
+
# Logs
|
|
24
|
+
logs/
|
|
25
|
+
*.log
|
|
26
|
+
|
|
27
|
+
# Generated reports
|
|
28
|
+
reports/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/gitleaks/gitleaks
|
|
3
|
+
rev: v8.30.1
|
|
4
|
+
hooks:
|
|
5
|
+
- id: gitleaks # staged changes only; CI (security.yml) scans the whole history
|
|
6
|
+
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
|
7
|
+
rev: v1.30.1
|
|
8
|
+
hooks:
|
|
9
|
+
- id: zizmor # workflow security: template injection, unpinned actions, pull_request_target
|
|
10
|
+
- repo: https://github.com/rhysd/actionlint
|
|
11
|
+
rev: v1.7.12
|
|
12
|
+
hooks:
|
|
13
|
+
- id: actionlint # workflow syntax and shellcheck of run: steps
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions follow [Semantic Versioning](https://semver.org/).
|
|
4
|
+
|
|
5
|
+
## [0.1.2] - 2026-10-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Published on PyPI (`uvx buysmarter`) via trusted publishing; package metadata: license, authors, project URLs.
|
|
9
|
+
|
|
10
|
+
## [0.1.1] - 2026-10-14
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- fastmcp 4.1 (MCP SDK v2): the MCP server answers both the handshake-era and the sessionless protocol; tools and
|
|
14
|
+
their schemas are unchanged.
|
|
15
|
+
- Reports go to `~/.local/share/buysmarter/reports/` and logs to `~/.cache/buysmarter/logs/`, not to the folder the
|
|
16
|
+
command runs in.
|
|
17
|
+
|
|
18
|
+
### Security
|
|
19
|
+
- pyjwt 2.15.1, cryptography 50.0.2 and mako 1.4.3 close the open advisories (1 critical, 6 high).
|
|
20
|
+
|
|
21
|
+
### Dependencies
|
|
22
|
+
- pydantic 2.13.5, pydantic-settings 2.15.0, fastapi 0.142.2, asyncpg 0.32.0.
|
|
23
|
+
|
|
24
|
+
## [0.1.0] - 2026-10-14
|
|
25
|
+
|
|
26
|
+
First release.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- `recommend`: a request in plain words → offers from Kaspi.kz, Ozon.kz and OLX.kz (used) ranked for budget, city
|
|
30
|
+
and required features, with a score per axis (brand or ingredient, price with a 30-day trend, reviews, specs,
|
|
31
|
+
popularity, availability) and a markdown report.
|
|
32
|
+
- Review trust: five-star share over the full distribution, review bursts, junk texts and defect complaints
|
|
33
|
+
discount the reviews axis; required features are checked against verbatim review quotes.
|
|
34
|
+
- Brand and model knowledge per category (`learn-category`, `learn`), seeded for water flossers.
|
|
35
|
+
- Smartphones: Kimovil index and phone specs in the specs axis.
|
|
36
|
+
- OLX used listings: defects the seller admits to are listed as cons; ads sold for parts are dropped.
|
|
37
|
+
- Price history (`refresh`, `history`) in Postgres; everything else works without it.
|
|
38
|
+
- MCP server for Claude over the same services.
|
|
39
|
+
- `doctor`: what is missing and what that turns off.
|
|
40
|
+
- One-command installers for Linux, macOS (`install.sh`) and Windows (`install.ps1`); settings also read
|
|
41
|
+
`~/.config/buysmarter/.env`.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Code of conduct
|
|
2
|
+
|
|
3
|
+
This project follows the [Contributor Covenant 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
|
|
4
|
+
Report unacceptable behavior privately through
|
|
5
|
+
[GitHub security advisories](https://github.com/octrow/buy-smarter/security/advisories/new) or to the maintainer's
|
|
6
|
+
GitHub profile email.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Кодекс поведения
|
|
2
|
+
|
|
3
|
+
Проект следует [Contributor Covenant 2.1](https://www.contributor-covenant.org/ru/version/2/1/code_of_conduct/).
|
|
4
|
+
О недопустимом поведении сообщайте приватно через
|
|
5
|
+
[GitHub security advisories](https://github.com/octrow/buy-smarter/security/advisories/new) или на почту из
|
|
6
|
+
GitHub-профиля мейнтейнера.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
**English** | **[Русский](CONTRIBUTING.ru.md)**
|
|
2
|
+
|
|
3
|
+
# Contributing
|
|
4
|
+
|
|
5
|
+
Bug reports, new categories of brand knowledge and source fixes are welcome.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv sync
|
|
11
|
+
make check # ruff check + ruff format --check + pytest
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Tests need no network. Database tests run when Postgres is up (`make up && uv run alembic upgrade head`)
|
|
15
|
+
and are skipped otherwise.
|
|
16
|
+
|
|
17
|
+
## Pull requests
|
|
18
|
+
|
|
19
|
+
- One change per PR, with a test that fails without it.
|
|
20
|
+
- Parser changes come with a saved response in `tests/samples/` (strip personal data first).
|
|
21
|
+
- Commit messages: `<type>: <description>`, where type is one of feat, fix, refactor, docs, test, chore, perf, ci.
|
|
22
|
+
- Marketplace adapters live in [market-sources](https://github.com/octrow/market-sources); a broken
|
|
23
|
+
Kaspi/Ozon/OLX parser is usually fixed there.
|
|
24
|
+
- Changes to `README.md` go into `README.ru.md` too, and the other way round.
|
|
25
|
+
|
|
26
|
+
Be polite to the marketplaces: keep request pacing as it is and don't add bulk crawling.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
**[English](CONTRIBUTING.md)** | **Русский**
|
|
2
|
+
|
|
3
|
+
# Как помочь проекту
|
|
4
|
+
|
|
5
|
+
Приветствуются баг-репорты, знания о брендах для новых категорий и починка источников.
|
|
6
|
+
|
|
7
|
+
## Подготовка
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv sync
|
|
11
|
+
make check # ruff check + ruff format --check + pytest
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Тестам сеть не нужна. Тесты базы запускаются, когда поднят Postgres (`make up && uv run alembic upgrade head`),
|
|
15
|
+
иначе пропускаются.
|
|
16
|
+
|
|
17
|
+
## Pull request
|
|
18
|
+
|
|
19
|
+
- Одно изменение на PR и тест, который без него падает.
|
|
20
|
+
- Изменения парсера идут вместе с сохранённым ответом в `tests/samples/` (без персональных данных).
|
|
21
|
+
- Сообщения коммитов: `<type>: <description>`, где type — feat, fix, refactor, docs, test, chore, perf или ci.
|
|
22
|
+
- Адаптеры площадок живут в [market-sources](https://github.com/octrow/market-sources); сломанный парсер
|
|
23
|
+
Kaspi/Ozon/OLX обычно чинится там.
|
|
24
|
+
- Изменения в `README.md` дублируются в `README.ru.md`, и наоборот.
|
|
25
|
+
|
|
26
|
+
Бережно относитесь к площадкам: не ускоряйте запросы и не добавляйте массовый обход.
|
buysmarter-0.1.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniil Petrov
|
|
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.
|