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.
Files changed (170) hide show
  1. buysmarter-0.1.2/.env.example +19 -0
  2. buysmarter-0.1.2/.github/ISSUE_TEMPLATE/bug.yml +28 -0
  3. buysmarter-0.1.2/.github/ISSUE_TEMPLATE/config.yml +5 -0
  4. buysmarter-0.1.2/.github/ISSUE_TEMPLATE/feature.yml +10 -0
  5. buysmarter-0.1.2/.github/dependabot.yml +14 -0
  6. buysmarter-0.1.2/.github/pull_request_template.md +6 -0
  7. buysmarter-0.1.2/.github/workflows/ci.yml +34 -0
  8. buysmarter-0.1.2/.github/workflows/install.yml +36 -0
  9. buysmarter-0.1.2/.github/workflows/release.yml +79 -0
  10. buysmarter-0.1.2/.github/workflows/scorecard.yml +30 -0
  11. buysmarter-0.1.2/.github/workflows/security.yml +36 -0
  12. buysmarter-0.1.2/.gitignore +28 -0
  13. buysmarter-0.1.2/.gitleaks.toml +6 -0
  14. buysmarter-0.1.2/.mcp.json +13 -0
  15. buysmarter-0.1.2/.pre-commit-config.yaml +13 -0
  16. buysmarter-0.1.2/.python-version +1 -0
  17. buysmarter-0.1.2/CHANGELOG.md +41 -0
  18. buysmarter-0.1.2/CODE_OF_CONDUCT.md +6 -0
  19. buysmarter-0.1.2/CODE_OF_CONDUCT.ru.md +6 -0
  20. buysmarter-0.1.2/CONTRIBUTING.md +26 -0
  21. buysmarter-0.1.2/CONTRIBUTING.ru.md +26 -0
  22. buysmarter-0.1.2/LICENSE +21 -0
  23. buysmarter-0.1.2/Makefile +15 -0
  24. buysmarter-0.1.2/PKG-INFO +238 -0
  25. buysmarter-0.1.2/README.md +211 -0
  26. buysmarter-0.1.2/README.ru.md +213 -0
  27. buysmarter-0.1.2/SECURITY.md +9 -0
  28. buysmarter-0.1.2/SECURITY.ru.md +9 -0
  29. buysmarter-0.1.2/THIRD_PARTY_NOTICES.md +32 -0
  30. buysmarter-0.1.2/alembic/README +1 -0
  31. buysmarter-0.1.2/alembic/env.py +94 -0
  32. buysmarter-0.1.2/alembic/script.py.mako +28 -0
  33. buysmarter-0.1.2/alembic/versions/05dbcfa0e44d_add_prompt_text_to_llm_cache.py +32 -0
  34. buysmarter-0.1.2/alembic/versions/3c9f1a2b7d4e_add_review_stats_to_price_snapshots.py +30 -0
  35. buysmarter-0.1.2/alembic/versions/8e237249c427_initial_schema.py +122 -0
  36. buysmarter-0.1.2/alembic.ini +150 -0
  37. buysmarter-0.1.2/assets/readme/hero.ru.svg +52 -0
  38. buysmarter-0.1.2/assets/readme/hero.svg +52 -0
  39. buysmarter-0.1.2/assets/readme/social-preview.html +6 -0
  40. buysmarter-0.1.2/assets/readme/social-preview.png +0 -0
  41. buysmarter-0.1.2/docker-compose.yml +32 -0
  42. buysmarter-0.1.2/docs/analogs/README.md +60 -0
  43. buysmarter-0.1.2/docs/analogs/kaspi-mcp-free_kaspipulse.md +58 -0
  44. buysmarter-0.1.2/docs/analogs/kaspi-skill.md +50 -0
  45. buysmarter-0.1.2/docs/analogs/shop-scout_ozon-mcp.md +73 -0
  46. buysmarter-0.1.2/docs/analogs/smart-shopper.md +40 -0
  47. buysmarter-0.1.2/docs/analogs/tls-impersonation-olx.md +28 -0
  48. buysmarter-0.1.2/docs/spikes/kaspi-spike.md +53 -0
  49. buysmarter-0.1.2/docs/spikes/olx-spike.md +109 -0
  50. buysmarter-0.1.2/docs/spikes/ozon-spike.md +101 -0
  51. buysmarter-0.1.2/install.ps1 +80 -0
  52. buysmarter-0.1.2/install.sh +87 -0
  53. buysmarter-0.1.2/pyproject.toml +67 -0
  54. buysmarter-0.1.2/src/buysmarter/__init__.py +3 -0
  55. buysmarter-0.1.2/src/buysmarter/api/__init__.py +0 -0
  56. buysmarter-0.1.2/src/buysmarter/api/main.py +11 -0
  57. buysmarter-0.1.2/src/buysmarter/cli/__init__.py +0 -0
  58. buysmarter-0.1.2/src/buysmarter/cli/main.py +655 -0
  59. buysmarter-0.1.2/src/buysmarter/cli/report.py +259 -0
  60. buysmarter-0.1.2/src/buysmarter/config.py +49 -0
  61. buysmarter-0.1.2/src/buysmarter/db/__init__.py +0 -0
  62. buysmarter-0.1.2/src/buysmarter/db/base.py +39 -0
  63. buysmarter-0.1.2/src/buysmarter/db/models.py +123 -0
  64. buysmarter-0.1.2/src/buysmarter/db/repo.py +268 -0
  65. buysmarter-0.1.2/src/buysmarter/knowledge/__init__.py +9 -0
  66. buysmarter-0.1.2/src/buysmarter/knowledge/brands.py +247 -0
  67. buysmarter-0.1.2/src/buysmarter/knowledge/generate.py +205 -0
  68. buysmarter-0.1.2/src/buysmarter/knowledge/ingredients.py +131 -0
  69. buysmarter-0.1.2/src/buysmarter/knowledge/models_book.py +145 -0
  70. buysmarter-0.1.2/src/buysmarter/knowledge/store.py +341 -0
  71. buysmarter-0.1.2/src/buysmarter/llm/__init__.py +5 -0
  72. buysmarter-0.1.2/src/buysmarter/llm/adapter.py +448 -0
  73. buysmarter-0.1.2/src/buysmarter/llm/cache.py +188 -0
  74. buysmarter-0.1.2/src/buysmarter/llm/canonicalize.py +173 -0
  75. buysmarter-0.1.2/src/buysmarter/llm/ingredients.py +182 -0
  76. buysmarter-0.1.2/src/buysmarter/llm/jev.py +68 -0
  77. buysmarter-0.1.2/src/buysmarter/llm/models.py +35 -0
  78. buysmarter-0.1.2/src/buysmarter/llm/prompts.py +127 -0
  79. buysmarter-0.1.2/src/buysmarter/llm/relevance.py +192 -0
  80. buysmarter-0.1.2/src/buysmarter/llm/review_summary.py +383 -0
  81. buysmarter-0.1.2/src/buysmarter/logsetup.py +92 -0
  82. buysmarter-0.1.2/src/buysmarter/mcp_server/__init__.py +0 -0
  83. buysmarter-0.1.2/src/buysmarter/mcp_server/__main__.py +9 -0
  84. buysmarter-0.1.2/src/buysmarter/mcp_server/server.py +259 -0
  85. buysmarter-0.1.2/src/buysmarter/memory/__init__.py +0 -0
  86. buysmarter-0.1.2/src/buysmarter/memory/prefs.py +81 -0
  87. buysmarter-0.1.2/src/buysmarter/normalize/__init__.py +0 -0
  88. buysmarter-0.1.2/src/buysmarter/normalize/candidate.py +119 -0
  89. buysmarter-0.1.2/src/buysmarter/ranking/__init__.py +0 -0
  90. buysmarter-0.1.2/src/buysmarter/ranking/delivery.py +91 -0
  91. buysmarter-0.1.2/src/buysmarter/ranking/engine.py +1317 -0
  92. buysmarter-0.1.2/src/buysmarter/ranking/model_tokens.py +48 -0
  93. buysmarter-0.1.2/src/buysmarter/ranking/models.py +160 -0
  94. buysmarter-0.1.2/src/buysmarter/ranking/review_requirements.py +62 -0
  95. buysmarter-0.1.2/src/buysmarter/ranking/review_trust.py +311 -0
  96. buysmarter-0.1.2/src/buysmarter/ranking/spec_conflicts.py +76 -0
  97. buysmarter-0.1.2/src/buysmarter/ranking/used_defects.py +55 -0
  98. buysmarter-0.1.2/src/buysmarter/ranking/velocity.py +42 -0
  99. buysmarter-0.1.2/src/buysmarter/services/__init__.py +0 -0
  100. buysmarter-0.1.2/src/buysmarter/services/phone_specs.py +202 -0
  101. buysmarter-0.1.2/src/buysmarter/services/recommend.py +2107 -0
  102. buysmarter-0.1.2/tests/__init__.py +0 -0
  103. buysmarter-0.1.2/tests/cli/test_doctor.py +28 -0
  104. buysmarter-0.1.2/tests/cli/test_learn.py +145 -0
  105. buysmarter-0.1.2/tests/cli/test_main.py +345 -0
  106. buysmarter-0.1.2/tests/cli/test_report.py +224 -0
  107. buysmarter-0.1.2/tests/conftest.py +89 -0
  108. buysmarter-0.1.2/tests/db/__init__.py +0 -0
  109. buysmarter-0.1.2/tests/db/test_price_snapshot_model.py +8 -0
  110. buysmarter-0.1.2/tests/db/test_repo.py +133 -0
  111. buysmarter-0.1.2/tests/db/test_tracked_categories.py +23 -0
  112. buysmarter-0.1.2/tests/knowledge/__init__.py +0 -0
  113. buysmarter-0.1.2/tests/knowledge/test_brands.py +133 -0
  114. buysmarter-0.1.2/tests/knowledge/test_generate.py +121 -0
  115. buysmarter-0.1.2/tests/knowledge/test_ingredients.py +114 -0
  116. buysmarter-0.1.2/tests/knowledge/test_models_book.py +78 -0
  117. buysmarter-0.1.2/tests/knowledge/test_store.py +214 -0
  118. buysmarter-0.1.2/tests/llm/__init__.py +0 -0
  119. buysmarter-0.1.2/tests/llm/test_adapter.py +380 -0
  120. buysmarter-0.1.2/tests/llm/test_cache.py +114 -0
  121. buysmarter-0.1.2/tests/llm/test_canonicalize.py +167 -0
  122. buysmarter-0.1.2/tests/llm/test_ingredients.py +157 -0
  123. buysmarter-0.1.2/tests/llm/test_jev.py +41 -0
  124. buysmarter-0.1.2/tests/llm/test_prompts.py +64 -0
  125. buysmarter-0.1.2/tests/llm/test_relevance.py +161 -0
  126. buysmarter-0.1.2/tests/llm/test_review_summary.py +412 -0
  127. buysmarter-0.1.2/tests/mcp_server/__init__.py +0 -0
  128. buysmarter-0.1.2/tests/mcp_server/test_tools.py +235 -0
  129. buysmarter-0.1.2/tests/memory/__init__.py +0 -0
  130. buysmarter-0.1.2/tests/memory/test_prefs.py +12 -0
  131. buysmarter-0.1.2/tests/normalize/__init__.py +0 -0
  132. buysmarter-0.1.2/tests/normalize/test_candidate.py +116 -0
  133. buysmarter-0.1.2/tests/ranking/__init__.py +0 -0
  134. buysmarter-0.1.2/tests/ranking/test_delivery.py +132 -0
  135. buysmarter-0.1.2/tests/ranking/test_engine.py +1358 -0
  136. buysmarter-0.1.2/tests/ranking/test_ingredient_axis.py +154 -0
  137. buysmarter-0.1.2/tests/ranking/test_model_relevance.py +96 -0
  138. buysmarter-0.1.2/tests/ranking/test_review_requirements.py +76 -0
  139. buysmarter-0.1.2/tests/ranking/test_review_trust.py +270 -0
  140. buysmarter-0.1.2/tests/ranking/test_spec_conflicts.py +77 -0
  141. buysmarter-0.1.2/tests/ranking/test_substance_filter.py +135 -0
  142. buysmarter-0.1.2/tests/ranking/test_used_defects.py +51 -0
  143. buysmarter-0.1.2/tests/ranking/test_velocity.py +37 -0
  144. buysmarter-0.1.2/tests/samples/kaspi/offers.json +103 -0
  145. buysmarter-0.1.2/tests/samples/kaspi/reviews.json +221 -0
  146. buysmarter-0.1.2/tests/samples/kaspi/search.json +1174 -0
  147. buysmarter-0.1.2/tests/samples/kaspi/specs.json +355 -0
  148. buysmarter-0.1.2/tests/samples/olx/detail.json +145 -0
  149. buysmarter-0.1.2/tests/samples/olx/search.json +636 -0
  150. buysmarter-0.1.2/tests/samples/ozon/product.json +1605 -0
  151. buysmarter-0.1.2/tests/samples/ozon/reviews.json +1270 -0
  152. buysmarter-0.1.2/tests/samples/ozon/search.json +818 -0
  153. buysmarter-0.1.2/tests/services/__init__.py +0 -0
  154. buysmarter-0.1.2/tests/services/test_phone_specs.py +138 -0
  155. buysmarter-0.1.2/tests/services/test_query_constraints.py +25 -0
  156. buysmarter-0.1.2/tests/services/test_recommend.py +1523 -0
  157. buysmarter-0.1.2/tests/services/test_recommend_canonicalize.py +154 -0
  158. buysmarter-0.1.2/tests/services/test_recommend_composition.py +63 -0
  159. buysmarter-0.1.2/tests/services/test_recommend_concurrency.py +51 -0
  160. buysmarter-0.1.2/tests/services/test_recommend_logging.py +20 -0
  161. buysmarter-0.1.2/tests/services/test_recommend_substance.py +199 -0
  162. buysmarter-0.1.2/tests/services/test_relevance_filter.py +73 -0
  163. buysmarter-0.1.2/tests/sources/__init__.py +0 -0
  164. buysmarter-0.1.2/tests/sources/test_kaspi_parse.py +189 -0
  165. buysmarter-0.1.2/tests/sources/test_olx_parse.py +102 -0
  166. buysmarter-0.1.2/tests/sources/test_ozon_parse.py +143 -0
  167. buysmarter-0.1.2/tests/test_config.py +10 -0
  168. buysmarter-0.1.2/tests/test_logsetup.py +134 -0
  169. buysmarter-0.1.2/tests/test_smoke.py +23 -0
  170. 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,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Security vulnerability
4
+ url: https://github.com/octrow/buy-smarter/security/advisories/new
5
+ about: Report privately, not in a public issue
@@ -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,6 @@
1
+ ## What and why
2
+
3
+ ## Test plan
4
+
5
+ - [ ] `make check` passes
6
+ - [ ] README.md and README.ru.md updated together (if touched)
@@ -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
+ # [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/octrow/buy-smarter/badge)](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,6 @@
1
+ [extend]
2
+ useDefault = true
3
+
4
+ [allowlist]
5
+ description = "Saved public marketplace responses used as parser fixtures"
6
+ paths = ['''tests/samples/''']
@@ -0,0 +1,13 @@
1
+ {
2
+ "mcpServers": {
3
+ "buy-smarter": {
4
+ "command": "uv",
5
+ "args": [
6
+ "run",
7
+ "python",
8
+ "-m",
9
+ "buysmarter.mcp_server"
10
+ ]
11
+ }
12
+ }
13
+ }
@@ -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
+ Бережно относитесь к площадкам: не ускоряйте запросы и не добавляйте массовый обход.
@@ -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.
@@ -0,0 +1,15 @@
1
+ .PHONY: up down check test
2
+
3
+ up:
4
+ docker compose up -d
5
+
6
+ down:
7
+ docker compose down
8
+
9
+ check:
10
+ uv run ruff check .
11
+ uv run ruff format --check .
12
+ uv run pytest
13
+
14
+ test:
15
+ uv run pytest