sift-cli 1.0.1__tar.gz → 1.0.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 (164) hide show
  1. {sift_cli-1.0.1 → sift_cli-1.0.2}/PKG-INFO +6 -2
  2. {sift_cli-1.0.1 → sift_cli-1.0.2}/README.md +5 -1
  3. {sift_cli-1.0.1 → sift_cli-1.0.2}/pyproject.toml +4 -1
  4. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/__init__.py +1 -1
  5. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/cli.py +26 -0
  6. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/mutations.py +7 -0
  7. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_package.py +18 -0
  8. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_server.py +43 -0
  9. {sift_cli-1.0.1 → sift_cli-1.0.2}/.gitattributes +0 -0
  10. {sift_cli-1.0.1 → sift_cli-1.0.2}/.github/workflows/ci.yml +0 -0
  11. {sift_cli-1.0.1 → sift_cli-1.0.2}/.github/workflows/release.yml +0 -0
  12. {sift_cli-1.0.1 → sift_cli-1.0.2}/.gitignore +0 -0
  13. {sift_cli-1.0.1 → sift_cli-1.0.2}/LICENSE +0 -0
  14. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/00-PLAN.md +0 -0
  15. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/01-YAKALAMA.md +0 -0
  16. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/02-MODEL-KOPRUSU.md +0 -0
  17. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/03-DAMITMA.md +0 -0
  18. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/04-GUVENLIK-AGI.md +0 -0
  19. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/05-DIL-BAGIMSIZLIGI.md +0 -0
  20. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/06-DOSYA-TASLAGI.md +0 -0
  21. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/07-MCP-SUNUCUSU.md +0 -0
  22. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/08-BUTCE-VE-OLCUM.md +0 -0
  23. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/09-ARKA-PLAN-KOMUTLARI.md +0 -0
  24. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/10-GIZLILIK.md +0 -0
  25. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/11-YAYIN.md +0 -0
  26. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/12-CAGIRANIN-SOZ-HAKKI.md +0 -0
  27. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/13-DOSYA-DAMITMA.md +0 -0
  28. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/14-ARAYAN-PEEK.md +0 -0
  29. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/15-COKLU-IS.md +0 -0
  30. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/16-HAFIZA.md +0 -0
  31. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/17-YOGUN-ARACLAR.md +0 -0
  32. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/18-KAPSAM.md +0 -0
  33. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/19-KALINTI-MUTASYON.md +0 -0
  34. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/20-KAYIT-BIRIMI.md +0 -0
  35. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/21-SAKLAMA.md +0 -0
  36. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/22-YANIT-ONBELLEGI.md +0 -0
  37. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/23-OLCUM-BIRIMI.md +0 -0
  38. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/24-ANAHTAR.md +0 -0
  39. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/25-TEKLIF.md +0 -0
  40. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/26-BEKLEMEK.md +0 -0
  41. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/27-WINDOWS.md +0 -0
  42. {sift_cli-1.0.1 → sift_cli-1.0.2}/notlar/28-KURULUM.md +0 -0
  43. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/answers.py +0 -0
  44. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/background.py +0 -0
  45. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/capture.py +0 -0
  46. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/digest.py +0 -0
  47. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/distill.py +0 -0
  48. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/fallback.py +0 -0
  49. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/hook.py +0 -0
  50. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/lines.py +0 -0
  51. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/many.py +0 -0
  52. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/memory.py +0 -0
  53. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/model.py +0 -0
  54. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/outline.py +0 -0
  55. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/peek.py +0 -0
  56. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/privacy.py +0 -0
  57. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/records.py +0 -0
  58. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/server.py +0 -0
  59. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/store.py +0 -0
  60. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/tools.py +0 -0
  61. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/view.py +0 -0
  62. {sift_cli-1.0.1 → sift_cli-1.0.2}/src/sift/watch.py +0 -0
  63. {sift_cli-1.0.1 → sift_cli-1.0.2}/tanitim/00-PLAN.md +0 -0
  64. {sift_cli-1.0.1 → sift_cli-1.0.2}/tanitim/ARASTIRMA.md +0 -0
  65. {sift_cli-1.0.1 → sift_cli-1.0.2}/tanitim/T1-SURUM-1-0-1.md +0 -0
  66. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/budget.py +0 -0
  67. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/conftest.py +0 -0
  68. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/README.md +0 -0
  69. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/ansi-progress.json +0 -0
  70. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/ansi-progress.txt +0 -0
  71. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/cargo-de.json +0 -0
  72. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/cargo-de.txt +0 -0
  73. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/cmake-vi.json +0 -0
  74. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/cmake-vi.txt +0 -0
  75. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/cobol-mainframe.json +0 -0
  76. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/cobol-mainframe.txt +0 -0
  77. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/docker-ar.json +0 -0
  78. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/docker-ar.txt +0 -0
  79. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/dotnet-fr.json +0 -0
  80. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/dotnet-fr.txt +0 -0
  81. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/ffmpeg-fa.json +0 -0
  82. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/ffmpeg-fa.txt +0 -0
  83. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/gcc-ru.json +0 -0
  84. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/gcc-ru.txt +0 -0
  85. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/git-quiet.json +0 -0
  86. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/git-quiet.txt +0 -0
  87. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/gotest-zh.json +0 -0
  88. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/gotest-zh.txt +0 -0
  89. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/gradle-ko.json +0 -0
  90. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/gradle-ko.txt +0 -0
  91. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/journalctl-it.json +0 -0
  92. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/journalctl-it.txt +0 -0
  93. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/kubectl-hi.json +0 -0
  94. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/kubectl-hi.txt +0 -0
  95. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/latex-pl.json +0 -0
  96. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/latex-pl.txt +0 -0
  97. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/maven-tr.json +0 -0
  98. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/maven-tr.txt +0 -0
  99. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/mix-he.json +0 -0
  100. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/mix-he.txt +0 -0
  101. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/psql-es.json +0 -0
  102. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/psql-es.txt +0 -0
  103. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/pytest-en.json +0 -0
  104. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/pytest-en.txt +0 -0
  105. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/rspec-id.json +0 -0
  106. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/rspec-id.txt +0 -0
  107. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/terraform-pt.json +0 -0
  108. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/terraform-pt.txt +0 -0
  109. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/vite-ja.json +0 -0
  110. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/vite-ja.txt +0 -0
  111. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/zig-sw.json +0 -0
  112. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus/zig-sw.txt +0 -0
  113. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/clojure-rapor.json +0 -0
  114. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/clojure-rapor.txt +0 -0
  115. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/cpp-baslik.json +0 -0
  116. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/cpp-baslik.txt +0 -0
  117. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/elixir-onbellek.json +0 -0
  118. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/elixir-onbellek.txt +0 -0
  119. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/go-kuyruk.json +0 -0
  120. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/go-kuyruk.txt +0 -0
  121. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/haskell-ayristirici.json +0 -0
  122. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/haskell-ayristirici.txt +0 -0
  123. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/hcl-altyapi.json +0 -0
  124. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/hcl-altyapi.txt +0 -0
  125. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/java-depo.json +0 -0
  126. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/java-depo.txt +0 -0
  127. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/makefile-yapi.json +0 -0
  128. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/makefile-yapi.txt +0 -0
  129. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/python-akis.json +0 -0
  130. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/python-akis.txt +0 -0
  131. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/qqzz-uydurma.json +0 -0
  132. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/qqzz-uydurma.txt +0 -0
  133. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/ruby-fatura.json +0 -0
  134. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/ruby-fatura.txt +0 -0
  135. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/rust-matris.json +0 -0
  136. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/rust-matris.txt +0 -0
  137. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/shell-dagitim.json +0 -0
  138. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/shell-dagitim.txt +0 -0
  139. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/sql-sema.json +0 -0
  140. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus-kaynak/sql-sema.txt +0 -0
  141. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/korpus_reader.py +0 -0
  142. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/languages.py +0 -0
  143. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/outlines.py +0 -0
  144. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_answers.py +0 -0
  145. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_background.py +0 -0
  146. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_capture.py +0 -0
  147. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_cli.py +0 -0
  148. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_digest.py +0 -0
  149. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_distill.py +0 -0
  150. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_fallback.py +0 -0
  151. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_gc.py +0 -0
  152. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_hook.py +0 -0
  153. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_hook_setup.py +0 -0
  154. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_kaynak.py +0 -0
  155. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_korpus.py +0 -0
  156. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_lines.py +0 -0
  157. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_many.py +0 -0
  158. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_model.py +0 -0
  159. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_outline.py +0 -0
  160. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_privacy.py +0 -0
  161. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_records.py +0 -0
  162. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_setup.py +0 -0
  163. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/test_tokens.py +0 -0
  164. {sift_cli-1.0.1 → sift_cli-1.0.2}/test/wire.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sift-cli
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: Runs your command, then gives the model only the lines that matter.
5
5
  Project-URL: Homepage, https://github.com/slymnysr/sift
6
6
  Author: slmnys
@@ -182,6 +182,7 @@ sift outline PATH what a file declares, without its bodies
182
182
  sift digest PATH... what is in files somebody else produced
183
183
  sift peek HANDLE|PATH [FIRST] [LAST]
184
184
  sift hook answer one shell-command event on stdin
185
+ sift mcp speak the protocol on stdin, for a client
185
186
  sift tools which dense tools this machine has
186
187
  sift tool NAME [ARGS...] run one of them, distilled
187
188
  sift memory [TERM] what has been run here before, and how it went
@@ -260,7 +261,10 @@ Without a key the server starts and every tool that would need a model declines
260
261
  with an explanation, so the agent falls back to its own shell rather than being
261
262
  handed a worse answer it cannot tell apart from a good one.
262
263
 
263
- Any client that speaks stdio will do — the command is `sift-mcp`. It offers
264
+ Any client that speaks stdio will do. The command is `sift-mcp`, and `sift mcp`
265
+ starts the same server — one word for a client that would rather run the package
266
+ by its own name (`uvx --from "sift-cli[mcp]" sift-cli mcp`, which is what the MCP
267
+ registry entry says). It offers
264
268
  `run`, `follow`, `outline`, `digest`, `digest_many`, `tool` and `peek`. `list`
265
269
  and `stats` are deliberately not offered: they would hand a model every command lately run on this machine,
266
270
  including the ones it never asked about, and the person at a terminal already
@@ -162,6 +162,7 @@ sift outline PATH what a file declares, without its bodies
162
162
  sift digest PATH... what is in files somebody else produced
163
163
  sift peek HANDLE|PATH [FIRST] [LAST]
164
164
  sift hook answer one shell-command event on stdin
165
+ sift mcp speak the protocol on stdin, for a client
165
166
  sift tools which dense tools this machine has
166
167
  sift tool NAME [ARGS...] run one of them, distilled
167
168
  sift memory [TERM] what has been run here before, and how it went
@@ -240,7 +241,10 @@ Without a key the server starts and every tool that would need a model declines
240
241
  with an explanation, so the agent falls back to its own shell rather than being
241
242
  handed a worse answer it cannot tell apart from a good one.
242
243
 
243
- Any client that speaks stdio will do — the command is `sift-mcp`. It offers
244
+ Any client that speaks stdio will do. The command is `sift-mcp`, and `sift mcp`
245
+ starts the same server — one word for a client that would rather run the package
246
+ by its own name (`uvx --from "sift-cli[mcp]" sift-cli mcp`, which is what the MCP
247
+ registry entry says). It offers
244
248
  `run`, `follow`, `outline`, `digest`, `digest_many`, `tool` and `peek`. `list`
245
249
  and `stats` are deliberately not offered: they would hand a model every command lately run on this machine,
246
250
  including the ones it never asked about, and the person at a terminal already
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sift-cli"
7
- version = "1.0.1"
7
+ version = "1.0.2"
8
8
  description = "Runs your command, then gives the model only the lines that matter."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -30,6 +30,9 @@ mcp = ["mcp>=2"]
30
30
  [project.scripts]
31
31
  sift = "sift.cli:main"
32
32
  sift-mcp = "sift.server:main"
33
+ # The package name, spelled as a command, because that is the only name a
34
+ # registry entry can carry: `uvx <package>` is how a client starts one.
35
+ sift-cli = "sift.cli:main"
33
36
 
34
37
  [project.urls]
35
38
  Homepage = "https://github.com/slymnysr/sift"
@@ -15,4 +15,4 @@ reply that makes no sense -- each of these falls back to rules that need none of
15
15
  them. A tool that drops output to save context has cost more than it saved.
16
16
  """
17
17
 
18
- __version__ = "1.0.1"
18
+ __version__ = "1.0.2"
@@ -73,6 +73,7 @@ USAGE = """sift -- run a command, keep every byte, show the lines that matter
73
73
  sift hook answer one shell-command event on stdin
74
74
  sift hook --install [--yes] route the client's own shell here too
75
75
  sift hook --uninstall and take it back out
76
+ sift mcp speak the protocol on stdin, for a client
76
77
  sift tools which dense tools this machine has
77
78
  sift tool NAME [ARGS...] run one of them, distilled
78
79
  sift memory [TERM] [--here]
@@ -161,6 +162,8 @@ def main(argv: list[str] | None = None) -> int:
161
162
  return _list(rest)
162
163
  if word == "hook":
163
164
  return _hook(rest)
165
+ if word == "mcp":
166
+ return _mcp(rest)
164
167
  if word == "tools":
165
168
  return _tools()
166
169
  if word == "tool":
@@ -573,6 +576,29 @@ def _hook(args: list[str]) -> int:
573
576
  return 0
574
577
 
575
578
 
579
+ def _mcp(args: list[str]) -> int:
580
+ """Speak the protocol on stdin -- the same server `sift-mcp` starts.
581
+
582
+ Two names for one server looks like waste until you try to write down how a
583
+ stranger's client should start it. What a registry entry can name is the
584
+ package, and the package is `sift-cli`; what `uvx` runs is a command of that
585
+ same name. So `uvx --from "sift-cli[mcp]" sift-cli mcp` is a line anyone can
586
+ build from the entry alone, and it needs this word to exist.
587
+
588
+ The import is here rather than at the top because the whole point of the
589
+ extra is that the command line works without it. Somebody who never
590
+ installed `mcp` gets the sentence `sift.server` raises, not a traceback.
591
+ """
592
+ if args:
593
+ print(f"sift: mcp takes no arguments\n\n{USAGE}", file=sys.stderr)
594
+ return 2
595
+
596
+ from sift.server import main as serve
597
+
598
+ serve()
599
+ return 0
600
+
601
+
576
602
  def _tools() -> int:
577
603
  """What each of them replaces, and whether this machine has it.
578
604
 
@@ -1255,6 +1255,13 @@ MUTATIONS = [
1255
1255
  " if not wanted():\n return PASS",
1256
1256
  " if False:\n return PASS",
1257
1257
  ),
1258
+ # -- T1/T2: the word a registry entry can spell ----------------------------
1259
+ Mutation(
1260
+ "cli.py",
1261
+ "the server can be started by the package's own name",
1262
+ ' if word == "mcp":\n return _mcp(rest)\n',
1263
+ "",
1264
+ ),
1258
1265
  ]
1259
1266
 
1260
1267
 
@@ -23,6 +23,24 @@ def test_the_published_version_is_the_one_the_package_reports():
23
23
  assert veri["project"]["version"] == sift.__version__
24
24
 
25
25
 
26
+ def test_a_command_is_named_after_the_package_itself():
27
+ """`uvx sift-cli` has to start something.
28
+
29
+ What an MCP registry entry can name is the package, and what `uvx` runs is a
30
+ command of that same name -- so a client building a command from the entry
31
+ alone types `sift-cli`, and until this existed it got "an executable named
32
+ `sift-cli` is not provided by package `sift-cli`". The names differ because
33
+ `sift` was taken on PyPI, which is nobody's fault and still the user's
34
+ problem.
35
+ """
36
+ veri = tomllib.loads((KOK / "pyproject.toml").read_text(encoding="utf-8"))
37
+ scripts = veri["project"]["scripts"]
38
+ ad = veri["project"]["name"]
39
+
40
+ assert ad in scripts, f"{ad} adinda bir komut yok; uvx {ad} calismaz"
41
+ assert scripts[ad] == scripts["sift"], "paketin adi komut satirini baslatmali"
42
+
43
+
26
44
  def test_notes_and_tests_live_in_their_own_folders():
27
45
  """The layout the project promised: notes in notlar/, tests in test/.
28
46
 
@@ -268,6 +268,49 @@ def test_and_the_test_above_would_have_noticed():
268
268
  assert "`sift`) is already installed" in finished.stderr
269
269
 
270
270
 
271
+ # -- the word a registry entry can spell -------------------------------------
272
+
273
+
274
+ def test_the_subcommand_starts_the_same_server(monkeypatch):
275
+ """`sift mcp` and `sift-mcp` are one server under two names.
276
+
277
+ The second name exists because an MCP registry entry can only name the
278
+ package, and a client turns that name into `uvx --from "sift-cli[mcp]"
279
+ sift-cli mcp`. A word that a stranger's client types has to reach the same
280
+ place the documented command does, or the entry is worse than no entry.
281
+ """
282
+ from sift import cli
283
+
284
+ started = []
285
+ monkeypatch.setattr(s, "main", lambda: started.append(True))
286
+
287
+ assert cli.main(["mcp"]) == 0
288
+ assert started == [True], "alt komut sunucuyu baslatmadi"
289
+
290
+
291
+ def test_the_subcommand_takes_nothing_and_says_so(capsys):
292
+ """Silence would be worse: a flag that was ignored looks like a flag that worked."""
293
+ from sift import cli
294
+
295
+ assert cli.main(["mcp", "--port", "8080"]) == 2
296
+ assert "mcp takes no arguments" in capsys.readouterr().err
297
+
298
+
299
+ def test_the_subcommand_says_the_same_sentence_when_the_extra_is_missing():
300
+ """The registry command minus the extra is an ordinary thing to type.
301
+
302
+ `uvx sift-cli mcp` -- without `--from "sift-cli[mcp]"` -- installs the
303
+ command line and nothing else, and this is the path a client takes when it
304
+ drops the runtime arguments. What comes back has to be the sentence, not a
305
+ stack: the import lives inside the subcommand for exactly this reason.
306
+ """
307
+ finished = _without_mcp("import sift.cli\nsift.cli.main(['mcp'])\n")
308
+
309
+ assert finished.returncode != 0
310
+ assert 'uv tool install "sift-cli[mcp]"' in finished.stderr
311
+ assert "Traceback" not in finished.stderr
312
+
313
+
271
314
  # -- over the real transport -------------------------------------------------
272
315
 
273
316
  SCRIPT = shutil.which("sift-mcp")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes