sud-cli 0.1.1__py3-none-any.whl

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 (74) hide show
  1. sud_cli/__init__.py +3 -0
  2. sud_cli/__main__.py +4 -0
  3. sud_cli/_wm.py +118 -0
  4. sud_cli/agenda.py +90 -0
  5. sud_cli/autofill.py +192 -0
  6. sud_cli/autofill_markers.json +56 -0
  7. sud_cli/backup.py +256 -0
  8. sud_cli/calendar_ics.py +191 -0
  9. sud_cli/case_diff.py +165 -0
  10. sud_cli/case_search_bridge.py +1322 -0
  11. sud_cli/chain.py +226 -0
  12. sud_cli/cli.py +3721 -0
  13. sud_cli/completeness.py +105 -0
  14. sud_cli/config.py +20 -0
  15. sud_cli/conflict.py +210 -0
  16. sud_cli/conflicts.py +207 -0
  17. sud_cli/court_alerts.py +156 -0
  18. sud_cli/court_source.py +108 -0
  19. sud_cli/data/completeness_rules.json +18 -0
  20. sud_cli/data/key_rates.json +59 -0
  21. sud_cli/data/npa_historical_editions.json +19 -0
  22. sud_cli/db.py +2072 -0
  23. sud_cli/deadline_matrix.py +99 -0
  24. sud_cli/deadline_state.py +25 -0
  25. sud_cli/deadlines.py +8 -0
  26. sud_cli/deadlines_state.py +1 -0
  27. sud_cli/deferral.py +5 -0
  28. sud_cli/efrsb_manual.py +85 -0
  29. sud_cli/efrsb_public.py +210 -0
  30. sud_cli/egrul_nalog.py +150 -0
  31. sud_cli/explain.py +190 -0
  32. sud_cli/fees_extra.py +104 -0
  33. sud_cli/forms.py +204 -0
  34. sud_cli/fssp_manual.py +91 -0
  35. sud_cli/fssp_semi.py +285 -0
  36. sud_cli/fssp_stats.py +105 -0
  37. sud_cli/government_transfers.json +8 -0
  38. sud_cli/holidays_rf.json +62 -0
  39. sud_cli/idempotency.py +78 -0
  40. sud_cli/importing.py +575 -0
  41. sud_cli/interest.py +144 -0
  42. sud_cli/jurisdiction.py +177 -0
  43. sud_cli/jurisdiction_rules.json +265 -0
  44. sud_cli/legal_deadlines.py +278 -0
  45. sud_cli/legal_engine.py +558 -0
  46. sud_cli/legal_rules.json +1058 -0
  47. sud_cli/limitation.py +70 -0
  48. sud_cli/mcp_server.py +1594 -0
  49. sud_cli/metrics.py +142 -0
  50. sud_cli/npa_as_of.py +21 -0
  51. sud_cli/output.py +89 -0
  52. sud_cli/payment.py +24 -0
  53. sud_cli/pdn.py +259 -0
  54. sud_cli/plenum_corpus.py +134 -0
  55. sud_cli/plenums_seed.json +45 -0
  56. sud_cli/practice_status.py +136 -0
  57. sud_cli/pravo_publication.py +361 -0
  58. sud_cli/pretrial.py +151 -0
  59. sud_cli/production_calendar.py +233 -0
  60. sud_cli/providers.py +95 -0
  61. sud_cli/readiness.py +234 -0
  62. sud_cli/regional_holidays.py +37 -0
  63. sud_cli/reminders.py +92 -0
  64. sud_cli/reports.py +349 -0
  65. sud_cli/sud_netguard.py +943 -0
  66. sud_cli/timeline_audit.py +80 -0
  67. sud_cli/verify.py +201 -0
  68. sud_cli-0.1.1.dist-info/METADATA +119 -0
  69. sud_cli-0.1.1.dist-info/RECORD +74 -0
  70. sud_cli-0.1.1.dist-info/WHEEL +4 -0
  71. sud_cli-0.1.1.dist-info/entry_points.txt +2 -0
  72. sud_cli-0.1.1.dist-info/licenses/AUTHORS +5 -0
  73. sud_cli-0.1.1.dist-info/licenses/LICENSE.md +661 -0
  74. sud_cli-0.1.1.dist-info/licenses/NOTICE +7 -0
@@ -0,0 +1,80 @@
1
+ """Deterministic checks for procedural case timelines."""
2
+
3
+ from collections import defaultdict
4
+ from datetime import datetime
5
+
6
+
7
+ def _time(event):
8
+ return datetime.fromisoformat(str(event["timestamp"]).replace("Z", "+00:00"))
9
+
10
+
11
+ def _anomaly(code, severity, title, detail, basis, ids):
12
+ return {"code": code, "severity": severity, "title": title, "detail": detail,
13
+ "legal_basis": basis, "event_ids": list(dict.fromkeys(ids))}
14
+
15
+
16
+ def audit_timeline(events):
17
+ events = list(events)
18
+ ordered = sorted(events, key=_time)
19
+ anomalies = []
20
+ initiation = next((e for e in ordered if e.get("action", "").lower() in {
21
+ "initiation", "case_initiated", "возбуждение уголовного дела", "вуд"
22
+ } or e.get("doc_type", "").lower() in {"vud", "initiation"}), None)
23
+ if initiation:
24
+ for event in ordered:
25
+ action = f"{event.get('action', '')} {event.get('doc_type', '')}".lower()
26
+ if _time(event) < _time(initiation) and any(word in action for word in
27
+ ("search", "обыск", "interrogation", "допрос", "seizure", "выемка", "экспертиз")):
28
+ if not any(word in action for word in ("inspection", "осмотр места происшествия")):
29
+ anomalies.append(_anomaly("PRE_INITIATION_ACTION", "CRITICAL",
30
+ "Следственное действие до возбуждения дела",
31
+ f"{event.get('action', event.get('doc_type', ''))} проведено до ВУД",
32
+ "ст. 144, 146 УПК РФ", [event.get("id", ""), initiation.get("id", "")]))
33
+
34
+ delivered = {}
35
+ for event in ordered:
36
+ action = f"{event.get('action', '')} {event.get('doc_type', '')}".lower()
37
+ if any(word in action for word in ("доставлен", "delivered", "фактическое задержание")):
38
+ delivered[event.get("participant", event.get("suspect", ""))] = event
39
+ if "протокол задержания" in action or "detention_protocol" in action:
40
+ key = event.get("participant", event.get("suspect", ""))
41
+ prior = delivered.get(key)
42
+ if prior and (_time(event) - _time(prior)).total_seconds() > 3 * 3600:
43
+ anomalies.append(_anomaly("DETENTION_PROTOCOL_DELAY", "HIGH",
44
+ "Протокол задержания составлен с опозданием",
45
+ "между доставлением и протоколом прошло более 3 часов",
46
+ "ч. 1 ст. 92 УПК РФ", [prior.get("id", ""), event.get("id", "")]))
47
+
48
+ witness_events = defaultdict(list)
49
+ for event in events:
50
+ for witness in event.get("witnesses", []) or []:
51
+ witness_events[str(witness)].append(event)
52
+ address = event.get("location") or event.get("object_address")
53
+ for witness in event.get("witness_details", []) or []:
54
+ if address and witness.get("address") == address:
55
+ anomalies.append(_anomaly("REPEATED_OR_BIASED_WITNESS", "HIGH",
56
+ "Понятой может быть заинтересованным",
57
+ "адрес понятого совпадает с адресом объекта",
58
+ "ст. 60 УПК РФ", [event.get("id", "")]))
59
+ for witness, rows in witness_events.items():
60
+ if len(rows) > 1:
61
+ anomalies.append(_anomaly("REPEATED_OR_BIASED_WITNESS", "MEDIUM",
62
+ "Повторное участие понятого",
63
+ f"лицо {witness} указано в {len(rows)} процессуальных событиях",
64
+ "ст. 60 УПК РФ", [row.get("id", "") for row in rows]))
65
+
66
+ custody = defaultdict(list)
67
+ for event in ordered:
68
+ evidence = event.get("evidence_id")
69
+ if evidence:
70
+ custody[evidence].append(event)
71
+ for evidence, rows in custody.items():
72
+ for previous, current in zip(rows, rows[1:]):
73
+ if previous.get("custodian") and current.get("custodian") and previous["custodian"] != current["custodian"]:
74
+ documented = current.get("transfer_document") or current.get("transfer_doc_id") or current.get("document_id")
75
+ if not documented:
76
+ anomalies.append(_anomaly("CHAIN_OF_CUSTODY_BROKEN", "CRITICAL",
77
+ "Разрыв цепочки хранения вещдока",
78
+ f"{evidence}: {previous['custodian']} передал без документа {current['custodian']}",
79
+ "ст. 81, 82 УПК РФ", [previous.get("id", ""), current.get("id", "")]))
80
+ return {"status": "ok", "anomalies_count": len(anomalies), "anomalies": anomalies}
sud_cli/verify.py ADDED
@@ -0,0 +1,201 @@
1
+ """Quote verification (S-52, v2.9.0) — правило «тексты только из реестра»: «никогда не ok на глаз».
2
+
3
+ Pure offline functions: a quoted fragment is checked against an explicit local
4
+ source — a bundled historical NPA edition (``sud_cli.npa_as_of``), the events
5
+ table of a case, or a plain-text file. The verdict is one of:
6
+
7
+ exact the normalized fragment occurs literally in the source;
8
+ fuzzy no literal match, but a difflib similarity above the
9
+ ``1 - max_fuzzy`` threshold (only when max_fuzzy > 0);
10
+ not_found neither of the above — the honest default;
11
+ edition_unavailable the requested bundle edition/article/date is absent
12
+ (an NPA source NEVER degrades to not_found: absence of
13
+ the bundle is not absence of the norm).
14
+
15
+ Normalization is minimal and lossless for legal text: NBSP -> space, whitespace
16
+ runs -> single space, ё -> е, case-folded. Punctuation is deliberately NOT
17
+ stripped — dropping it is how a "looks the same" citation quietly becomes ok.
18
+ """
19
+
20
+ import difflib
21
+ import re
22
+ from pathlib import Path
23
+
24
+ SNIPPET_WINDOW = 80
25
+ DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
26
+
27
+ _WS_RE = re.compile(r"\s+")
28
+
29
+
30
+ class VerifyInputError(ValueError):
31
+ """Bad arguments for the verification itself (empty text, unknown source)."""
32
+
33
+
34
+ def normalize(text: str) -> str:
35
+ """Whitespace-tolerant, ё/е-insensitive, lowercased; punctuation kept."""
36
+ out = text.replace("\u00a0", " ").replace("\u202f", " ")
37
+ out = _WS_RE.sub(" ", out).strip()
38
+ return out.replace("\u0401", "\u0415").replace("\u0451", "\u0435").lower()
39
+
40
+
41
+ def _snippet(body: str, start: int, end: int) -> str:
42
+ lo = max(0, start - SNIPPET_WINDOW)
43
+ hi = min(len(body), end + SNIPPET_WINDOW)
44
+ prefix = "..." if lo > 0 else ""
45
+ suffix = "..." if hi < len(body) else ""
46
+ return prefix + body[lo:hi] + suffix
47
+
48
+
49
+ def _best_window(query: str, body: str):
50
+ """Best SequenceMatcher ratio over same-length-ish sliding windows."""
51
+ if not query or not body:
52
+ return 0.0, 0, 0
53
+ length = len(query)
54
+ lengths = sorted({w for w in range(max(1, length - 3), length + 4) if w <= len(body)})
55
+ if not lengths:
56
+ lengths = [len(body)]
57
+ step = 1 if len(body) <= 20000 else 4
58
+ best_ratio, best_start, best_len = 0.0, 0, lengths[0]
59
+ for wlen in lengths:
60
+ for i in range(0, len(body) - wlen + 1, step):
61
+ ratio = difflib.SequenceMatcher(None, query, body[i:i + wlen]).ratio()
62
+ if ratio > best_ratio:
63
+ best_ratio, best_start, best_len = ratio, i, wlen
64
+ if ratio >= 1.0:
65
+ return best_ratio, best_start, best_len
66
+ return best_ratio, best_start, best_len
67
+
68
+
69
+ def _match(norm_query: str, norm_body: str, max_fuzzy: float, location: str,
70
+ extra=None):
71
+ """Verdict for one normalized body. Exact only on a literal substring."""
72
+ base = {"location": location, "offset": None, "matched_fragment": None,
73
+ "ratio": None, "threshold": None, "snippet": None, "detail": None}
74
+ if extra:
75
+ base.update(extra)
76
+ idx = norm_body.find(norm_query)
77
+ if idx >= 0:
78
+ return {**base, "verdict": "exact", "offset": idx,
79
+ "matched_fragment": norm_body[idx:idx + len(norm_query)]}
80
+ if max_fuzzy and max_fuzzy > 0:
81
+ threshold = 1.0 - max_fuzzy
82
+ ratio, start, wlen = _best_window(norm_query, norm_body)
83
+ if ratio >= threshold:
84
+ return {**base, "verdict": "fuzzy", "offset": start, "ratio": round(ratio, 4),
85
+ "threshold": round(threshold, 4),
86
+ "matched_fragment": norm_body[start:start + wlen] or None,
87
+ "snippet": _snippet(norm_body, start, start + wlen)}
88
+ return {**base, "verdict": "not_found",
89
+ "detail": f"best fuzzy ratio {round(ratio, 4)} below threshold {round(threshold, 4)}"}
90
+ return {**base, "verdict": "not_found", "detail": "fuzzy matching disabled (max_fuzzy=0)"}
91
+
92
+
93
+ # ---------------------------------------------------------------- sources
94
+
95
+
96
+ def _source_npa(act: str, article: str, date: str, query: str, max_fuzzy: float):
97
+ from .npa_as_of import lookup_as_of
98
+
99
+ location = f"{act}/{article}/{date}"
100
+ result = lookup_as_of(act, article, date)
101
+ if result.get("status") != "ok":
102
+ # Absent bundle/edition is a coverage gap, never a "not found" claim.
103
+ return {"verdict": "edition_unavailable", "location": location, "offset": None,
104
+ "matched_fragment": None, "ratio": None, "threshold": None,
105
+ "snippet": None, "detail": result.get("detail", "edition not in offline bundle")}
106
+ return _match(query, normalize(result["text"]), max_fuzzy, location)
107
+
108
+
109
+ def _source_case(case_id: str, state_dir, query: str, max_fuzzy: float):
110
+ from .db import get_case_events, get_connection, init_db, list_case_documents
111
+
112
+ init_db(Path(state_dir))
113
+ conn = get_connection(Path(state_dir))
114
+ try:
115
+ events = get_case_events(conn, case_id)
116
+ docs = list_case_documents(conn, case_id)
117
+ finally:
118
+ conn.close()
119
+ # правило «тексты только из реестра»: покрытие прогона честное — приложенные документы дела
120
+ # сканируются наравне с событиями; нечитаемые (бинарь/нет файла) — явно
121
+ # в docs_skipped, не молча.
122
+ bodies = [(f"event:{e['event_id']}", normalize(e.get("doc_text") or "")) for e in events]
123
+ skipped = 0
124
+ for d in docs:
125
+ try:
126
+ raw = Path(d["path"]).read_text(encoding="utf-8")
127
+ except (OSError, UnicodeDecodeError):
128
+ skipped += 1
129
+ continue
130
+ bodies.append((f"doc:{d['id']}@{d['sha256'][:12]}", normalize(raw)))
131
+ scope = {"events_scanned": len(events), "docs_scanned": len(docs) - skipped,
132
+ "docs_skipped_unreadable": skipped}
133
+ best = {"verdict": "not_found", "location": None, "offset": None,
134
+ "matched_fragment": None, "ratio": None, "threshold": None,
135
+ "snippet": None,
136
+ "detail": (f"case {case_id}: " + ", ".join(f"{k}={v}" for k, v in scope.items())
137
+ + (" (нечитаемые документы НЕ проверялись — сверь формат)" if skipped else "")),
138
+ **scope}
139
+ for location, body in bodies:
140
+ verdict = _match(query, body, max_fuzzy, location, dict(scope))
141
+ if verdict["verdict"] == "exact":
142
+ return verdict
143
+ if verdict["verdict"] == "fuzzy" and best["verdict"] != "fuzzy":
144
+ best = verdict
145
+ return best
146
+
147
+
148
+ def _source_file(path, query: str, max_fuzzy: float):
149
+ p = Path(path)
150
+ raw = p.read_text(encoding="utf-8") # OSError propagates to the CLI layer
151
+ body = normalize(raw)
152
+ location = f"file:{p}"
153
+ found = _match(query, body, max_fuzzy, location)
154
+ if found["offset"] is not None:
155
+ found["location"] = f"{location}:{found['offset']}"
156
+ return found
157
+
158
+
159
+ # ---------------------------------------------------------------- entry
160
+
161
+
162
+ def verify_quote(text, source, *, act=None, article=None, date=None,
163
+ case_id=None, path=None, state_dir=None, max_fuzzy=0.0):
164
+ """Verify ``text`` against one local source. Raises VerifyInputError on
165
+ bad arguments; never touches the network."""
166
+ if not isinstance(text, str) or not text.strip():
167
+ raise VerifyInputError("text must be a non-empty string")
168
+ try:
169
+ max_fuzzy = float(max_fuzzy or 0.0)
170
+ except (TypeError, ValueError):
171
+ raise VerifyInputError("max-fuzzy must be a number")
172
+ if not 0.0 <= max_fuzzy <= 1.0:
173
+ raise VerifyInputError("max-fuzzy must be within 0..1")
174
+ query = normalize(text)
175
+ if not query:
176
+ raise VerifyInputError("text normalizes to empty string")
177
+
178
+ if source == "npa":
179
+ if not (act and article and date):
180
+ raise VerifyInputError("source npa requires --act, --article and --date")
181
+ if not DATE_RE.match(date):
182
+ raise VerifyInputError("date must be YYYY-MM-DD")
183
+ payload = _source_npa(act, article, date, query, max_fuzzy)
184
+ elif source == "case":
185
+ if not case_id:
186
+ raise VerifyInputError("source case requires --case-id")
187
+ if state_dir is None:
188
+ raise VerifyInputError("source case requires a state dir")
189
+ payload = _source_case(case_id, state_dir, query, max_fuzzy)
190
+ elif source == "file":
191
+ if not path:
192
+ raise VerifyInputError("source file requires --path")
193
+ if not Path(path).is_file():
194
+ raise VerifyInputError(f"file not found: {path}")
195
+ payload = _source_file(path, query, max_fuzzy)
196
+ else:
197
+ raise VerifyInputError(f"unknown source: {source!r}")
198
+
199
+ return {"source": source, "status": payload["verdict"],
200
+ "requested_text": text, "normalized_text": query,
201
+ "max_fuzzy": max_fuzzy, **payload}
@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.5
2
+ Name: sud-cli
3
+ Version: 0.1.1
4
+ Summary: Offline Phase 1 foundation for the sud-cli court-document toolkit.
5
+ Author-email: lnfnt <lnfnt@proton.me>
6
+ License-Expression: AGPL-3.0-only
7
+ License-File: AUTHORS
8
+ License-File: LICENSE.md
9
+ License-File: NOTICE
10
+ Keywords: cli,court,npa,offline
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Legal Industry
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+
20
+ # sud-cli
21
+
22
+ ![license](https://img.shields.io/badge/license-AGPL--3.0-orange) ![python](https://img.shields.io/badge/python-3.10%2B-blue) ![release](https://img.shields.io/badge/release-v0.1.1-informational) ![MCP](https://img.shields.io/badge/MCP-65%20tools-brightgreen) ![artifacts](https://img.shields.io/badge/artifacts-GPG--signed-green)
23
+
24
+
25
+ <p align="center"><img src="assets/poster.jpg" width="820" alt="sud-cli poster"></p>
26
+
27
+ CLI-инструмент для юридической работы с российскими судами: поиск практики и карточек дел,
28
+ процессуальные сроки, госпошлины, реестр доказательств, НПА. Offline-first: ядро работает без
29
+ сети, live-адаптеры ограничены документированными источниками. Встроенный MCP-сервер — 65
30
+ инструментов.
31
+
32
+ **Русский** · [<img src="assets/flags/gb.svg" height="16" align="top" alt="EN"> English](README.en.md)
33
+
34
+ ## Установка
35
+
36
+ ```bash
37
+ pip install sud-cli
38
+ sud-cli --help
39
+ ```
40
+
41
+ Из репозитория: `pip install git+https://github.com/lnfnt/sud-cli.git`.
42
+ Локальный checkout: `python3 -m venv .venv && . .venv/bin/activate && pip install .`
43
+ Python >= 3.10, рантайм-зависимостей нет.
44
+
45
+ ## Возможности
46
+
47
+ - **Поиск практики и дел** — единый запрос по официальным источникам (sudact,
48
+ publication.pravo.gov.ru, картотека арбитражных дел), карточки и таймлайны (КАД), подбор
49
+ практики по номеру дела и предмету спора.
50
+ - **Карточка контрагента (ЕГРЮЛ/ЕГРИП)** — открытые реестры ФНС (ст. 6 129-ФЗ), без капчи и
51
+ защищённых эндпоинтов.
52
+ - **Проверка банкротства** — основной путь через картотеку арбитражных дел; перепроверка ЕФРСБ
53
+ ручным контуром с фиксацией provenance (`efrsb link` / `efrsb record`).
54
+ - **ФССП, полу-автомат** — `fssp assist`: инструмент открывает банк данных, заполняет форму и
55
+ отправляет запрос; капчу решает человек (скриншот → код). Результат с provenance попадает в
56
+ реестр доказательств. Ручной контур `fssp check` доступен всегда.
57
+ - **Сроки и пошлины** — расчёт по ГПК/АПК/КАС РФ с производственным календарём; тарифы
58
+ госпошлин в проверяемых JSON-файлах правил.
59
+ - **Досье дела** — документы и версии, представители, доказательства, сроки, черновики;
60
+ экспорт; идемпотентный purge строго внутри state-директории.
61
+ - **Корпус постановлений ВС/ВАС** — `plenum list | plenum search`.
62
+ - **MCP-сервер** — `sud-cli mcp`, 65 инструментов для агентских рантаймов.
63
+ - **§13 AGPL** — `sud-cli license`: лицензия, provenance сборки, способ получить исходники.
64
+
65
+ ## Команды (Chain · Reps · Conflicts · Reports · Backup · Metrics · Purge)
66
+
67
+ ```
68
+ sud-cli deadlines chain А40-11111/25 # цепочка процессуальных сроков (Chain)
69
+ sud-cli case rep add | case rep list # представители по делу (Reps)
70
+ sud-cli calendar conflicts # конфликты слушаний (Conflicts)
71
+ sud-cli report portfolio / report case # отчёты (Reports)
72
+ sud-cli backup create | backup verify | backup restore # бэкап состояния (Backup)
73
+ sud-cli case export / case purge / metrics # экспорт, purge, метрики (Metrics, Purge)
74
+ sud-cli fssp assist --kind ur --inn 7704361225 # ФССП, полу-автомат
75
+ sud-cli efrsb link --kind ul --inn 7704361225 # ЕФРСБ, ручной контур
76
+ sud-cli plenum list | plenum search ТЕКСТ # корпус ВС/ВАС
77
+ sud-cli mcp # MCP-сервер (65 tools)
78
+ sud-cli license # §13 AGPL
79
+ ```
80
+
81
+ ## Разделы реестра дела
82
+
83
+ | Раздел | Содержание |
84
+ |---|---|
85
+ | **Evidence** | доказательства с provenance: кто, когда, метод, приложенный файл |
86
+ | **DocHistory** | версии процессуальных документов дела |
87
+ | **Checklist** | чек-листы из файлов правил |
88
+ | **Forms** | шаблоны и заполнение процессуальных форм |
89
+
90
+ ## Ограничения
91
+
92
+ - Автоподача не реализована и не планируется: документ в внешние системы отправляет человек.
93
+ - Практика ищётся по формальным признакам; оценку перспектив даёт юрист, не инструмент.
94
+ - Это не юридическая консультация: расчёт сроков, форматирование, поиск.
95
+
96
+ ## Сетевая политика
97
+
98
+ Live-адаптеры работают через ваш прокси (`SUD_EGRESS_PROXY`) с fail-closed DNS-pinning;
99
+ хост с ответом «лимит подключений» уходит в cooldown автоматически. Обход капчи автоматикой
100
+ запрещён конструкцией (см. ФССП/ЕФРСБ).
101
+
102
+ ## Проверка релиза
103
+
104
+ <details>
105
+ <summary>Контрольные суммы и GPG</summary>
106
+
107
+ ```bash
108
+ sha256sum -c SHA256SUMS
109
+ gpg --import release_pub.asc
110
+ gpg --verify sud_cli-0.1.1.tar.gz.asc # fingerprint F4492D47B75F12D025400C19291E4ACACA58902A
111
+ ```
112
+ </details>
113
+
114
+ ## Вклад и лицензия
115
+
116
+ AGPL-3.0-only; вклад требует DCO sign-off и права перелицензирования владельцем
117
+ (CONTRIBUTING.md). Уязвимости — приватно (SECURITY.md).
118
+
119
+ Автор и правообладатель: [lnfnt](mailto:lnfnt@proton.me).
@@ -0,0 +1,74 @@
1
+ sud_cli/__init__.py,sha256=tNwG1ZR3oqdfxrlWoK8lhKPFz0DoLubkNKc-8oHpjwo,88
2
+ sud_cli/__main__.py,sha256=qt9jVV7hfl6Ijw9sqqiLe1I7JJtdII4gFcJSJ5hf12I,86
3
+ sud_cli/_wm.py,sha256=HrGGkIaAiydK071v9ns1f1_5eWXzwquFtO5-Dloksc4,6664
4
+ sud_cli/agenda.py,sha256=bJu4e7KNNMxNwjP-IrktaVvScuHLrcUQytr5vQXh9gg,3244
5
+ sud_cli/autofill.py,sha256=lZhrkt_rZS7YvyaRycTEEFDsonuDqwkw4nkSoRPOMyc,8681
6
+ sud_cli/autofill_markers.json,sha256=KTVZFYttvTybrqKgofaZbVFQoWaI35A70Ym7gkRvjQM,1836
7
+ sud_cli/backup.py,sha256=aTSX7cBTQ_-_Ba8Gxd2xK6hhlvW0_pkICjqF7S5rPuo,9584
8
+ sud_cli/calendar_ics.py,sha256=C9J0Lo7XCw9NcYQd0TYuWEab9Wd4l_CRzy1vr7JDu8k,7617
9
+ sud_cli/case_diff.py,sha256=9ZgPnn8Ny_nN-0Tts0k5k5QQmy2A4DQK1QjTulE76dw,5847
10
+ sud_cli/case_search_bridge.py,sha256=WErvMM3ekPKUpugpv1LxN9C05ixW71TXSwAOVSogMJo,55190
11
+ sud_cli/chain.py,sha256=SRnNzsxoy7UjQnk1MoZmX0DyJZVD8siSf7jXwu8rbd0,11641
12
+ sud_cli/cli.py,sha256=9xCT9huU_hCgN3HX9dP5cH1YGpsc9ujg1gJhZcnId_M,163019
13
+ sud_cli/completeness.py,sha256=i3QsMFu6-yYEp6YbcyXeN7jJVjWxfBNfIu7IDkJf9NA,4883
14
+ sud_cli/config.py,sha256=vVVRXSYt5qEZYx9CbPC8joy80_hEdJnVLxC5KymNdoI,489
15
+ sud_cli/conflict.py,sha256=ISzD-pENTRC-2Zs50TVoF42dMLslaJNzoxjhc42hOAY,7658
16
+ sud_cli/conflicts.py,sha256=I3-Hp8gUgfga8bO4kMW7vXaltJ7dS9UXG0ZyBK8On4E,9308
17
+ sud_cli/court_alerts.py,sha256=xDP3_mX6w-fx15kXeeEyTElrkw4lMdZDQ_Kv7fBp198,6670
18
+ sud_cli/court_source.py,sha256=eGb_LlpUk9K93FT98MOQWKVcCu7NyEjr2bppkVVyEos,3119
19
+ sud_cli/db.py,sha256=cNUTYpmh5qZz4VdUICW3kb82O3inzjYsK8L3-D0pPGw,78654
20
+ sud_cli/deadline_matrix.py,sha256=_9DmXWvty_NSqKxeay_AfcWRBg6z4n1Z6zZ-BXHTHK4,3463
21
+ sud_cli/deadline_state.py,sha256=iL61Z_O-Q72z9fNucUlXf-DH8U79NrNH49Rikv5Pij4,611
22
+ sud_cli/deadlines.py,sha256=WxR1R01rk19H8SKTWEkl6hewj9Lr_DubIRUahbMJfKM,182
23
+ sud_cli/deadlines_state.py,sha256=e__eem7RI_G2NLYQXQ6Yr7AxQn_iLIbgVm-hjqrFOmM,30
24
+ sud_cli/deferral.py,sha256=kB_Xupmai4sOpMaA3uoW_u_fNU6JiGaWVyUz0vcYBRc,1303
25
+ sud_cli/efrsb_manual.py,sha256=kPz7sA6_oF9vc_u8Grg26PweVqX4OKgTM1KlDvc5Q8A,4379
26
+ sud_cli/efrsb_public.py,sha256=VVzWoRoduen0CPqxLvGXWquQOb3O1eQ0wdWCQhpbPqU,9807
27
+ sud_cli/egrul_nalog.py,sha256=JZ5574Yym0T5lUZ0X7E35g8ScMF6KmiPKnyeEG8jaDg,6416
28
+ sud_cli/explain.py,sha256=Axu77XKmC82scVSUwUKHhdZxFlJWpicxFz0yBsB09js,10342
29
+ sud_cli/fees_extra.py,sha256=zStzt31792hOS4bnKeEZ7_PL2GWOAeMJeldczvqOWqw,6170
30
+ sud_cli/forms.py,sha256=1tCpoiQdUosSydjGMqI_l_mLdSrGY47Cpqfh1Exu2jc,8666
31
+ sud_cli/fssp_manual.py,sha256=bQN9iuhk6UKma7wRgHCCj4R9Wjx_10RH_95r7ge3KFA,5656
32
+ sud_cli/fssp_semi.py,sha256=y5EkZKL3v-gRYHyWDW5H-nqM2a06uKayCyffCbfKqQQ,13763
33
+ sud_cli/fssp_stats.py,sha256=0egjMri8zZYgVIYt6raP6nk_DiH8r6L9Npev0wqvABU,4540
34
+ sud_cli/government_transfers.json,sha256=7vFwIw4ursx5k0DeJzN160swOxpBlFGt7VA_4g_mFFE,310
35
+ sud_cli/holidays_rf.json,sha256=Qbm3y614jbWT2ujnTI1GkUxOvwmdcFOmfU9MutpV0FI,6462
36
+ sud_cli/idempotency.py,sha256=E4gDPATm8GpXY3ICdCmTtQU5jrkTjRBS4_WazzsHv74,3292
37
+ sud_cli/importing.py,sha256=xaZVhry91bP6h7TxUqaWLzdnINGNo2B1Rw2svnAF1PY,20785
38
+ sud_cli/interest.py,sha256=fdT-YDI1eTcmH9UafXXAIZxCiTESlnySBB_AWCaRwEQ,6624
39
+ sud_cli/jurisdiction.py,sha256=853_iWTOnWnvlTy6kehRUlLve-YYJoV43TB7o4uLdm8,6822
40
+ sud_cli/jurisdiction_rules.json,sha256=GiHlZVP7mtgYX1KlA_oaMQVrRj5F1JTVdbnvKvZWIb0,14109
41
+ sud_cli/legal_deadlines.py,sha256=KGRsSCgUWGJHBdQx2ocd9U7glhh7mAijvu6I3hYXS2g,12168
42
+ sud_cli/legal_engine.py,sha256=tTJGzLyELd2OWqEnBpUMIPUHh9tS2GwXFKP_rkFpoik,24346
43
+ sud_cli/legal_rules.json,sha256=FZiedx4Jr-LXnp9J79IE7gL3igVqPBpAth9PpK7qL7s,49182
44
+ sud_cli/limitation.py,sha256=KJZeVclgQeiB7MqMO0Zdcvqi0veuLuvz5uKNvrRGbOI,2864
45
+ sud_cli/mcp_server.py,sha256=UQvhDm7xKv53d41L8MV8BnjK-t3nzlzrNbvzZE6r-0w,92404
46
+ sud_cli/metrics.py,sha256=K7F564wAzp97jAnQOvV83e9lQJhNAvdiz7ecthuUhjc,5506
47
+ sud_cli/npa_as_of.py,sha256=NUldTJHz-NLT4NZrpKFeLPtROPlqLYdj9Jc1rX_v580,998
48
+ sud_cli/output.py,sha256=8nLr-gr3qfJyd6hMNaHGrsIj6E-1qWZRTp8_-hxT31s,2832
49
+ sud_cli/payment.py,sha256=nv-gLA7QAHIh5Bb-DpOGwm-HSy8Nnp6SFB3UfdH73dE,1001
50
+ sud_cli/pdn.py,sha256=8TKMSn6CkQaRMcgiZAWCZ41vbvQ7ViagmNSF6z5mqkU,11140
51
+ sud_cli/plenum_corpus.py,sha256=9QCow-cqc5KkJoWUnDwb43VA8S0IaQkrjKac6iAweRk,5816
52
+ sud_cli/plenums_seed.json,sha256=quDHljO3wEMoOXnaga0XiucaXcaMNtlKSAxXzyh4StQ,2584
53
+ sud_cli/practice_status.py,sha256=jphTOvy8JIpomglQfJ3HRaTI3ktL2zAXWKK5J2pH47c,5788
54
+ sud_cli/pravo_publication.py,sha256=ccpn5T_qXIHA1ZGWLpOydKYeac8UNfkqPR5SkEK_fZ0,13947
55
+ sud_cli/pretrial.py,sha256=2Fi229SHVYAvfCgnekZzN-WVJqN2B4M-ZhKdlFYzII4,5659
56
+ sud_cli/production_calendar.py,sha256=gXfvyE4ecPqz_-E2cCdJtvPUTiMsoXrlJGVsqh74zYc,11010
57
+ sud_cli/providers.py,sha256=ro36-dVXj8eeV3pB9pmEK7Xd8oN_jtYzIBB-1HcYXfo,2768
58
+ sud_cli/readiness.py,sha256=SFmgIpMM0yfDi_v4CCMUMzr9tf7vKzJIjNwO3CnPMkU,9089
59
+ sud_cli/regional_holidays.py,sha256=H-iFgfVW8_79VYXm0lh2_6gdmHmQvtKlel0wfOYE2xQ,1899
60
+ sud_cli/reminders.py,sha256=Bf3uqtZEp5MQreFAVAfOEM59HVJTVaXvyyDxNSAdvm4,4006
61
+ sud_cli/reports.py,sha256=WtvM6G16V3ZAygtfjZslk3cxYeGdOqp8BrtgIuDTyXQ,15789
62
+ sud_cli/sud_netguard.py,sha256=blaf32ygNkZUmOhCIbeZR08uHIjbkXZS5rfZsxVyLlk,36378
63
+ sud_cli/timeline_audit.py,sha256=UfxDGbsAZ523pAlX3STvnnHttIZkl6E00-pSKMze2gw,4870
64
+ sud_cli/verify.py,sha256=Rk9VCZ4EeDt3FHxgBrWIjG0x9-U9zXXXoz2I1Ykdcok,9069
65
+ sud_cli/data/completeness_rules.json,sha256=kEM5Xj08Eb23RhjO_1ga9bNA0zON03k6nNFDVoXLNEM,1576
66
+ sud_cli/data/key_rates.json,sha256=cgCi8NHaec3g0TyvOje0UMBCAFfY6BbZidzYP1PKdNg,1012
67
+ sud_cli/data/npa_historical_editions.json,sha256=I4RFn8Elxj-Uz4FyZaZKnleosEfMWOySsKOx2Dx4HOc,3209
68
+ sud_cli-0.1.1.dist-info/METADATA,sha256=-TNG5Iej6bV8g1nXn6PMwYqEjcIjWUFwwM24-cgWt74,7162
69
+ sud_cli-0.1.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
70
+ sud_cli-0.1.1.dist-info/entry_points.txt,sha256=9sWJVvYr_EJHDw-pt5CcNddE2ksAhIsSSc8EzT6SW7s,45
71
+ sud_cli-0.1.1.dist-info/licenses/AUTHORS,sha256=Cj1riO_7PbZaUXoJPEDsRFcA2ZbuE3WmQ8FiCW3zYEY,99
72
+ sud_cli-0.1.1.dist-info/licenses/LICENSE.md,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
73
+ sud_cli-0.1.1.dist-info/licenses/NOTICE,sha256=1bN6U6PK344YOidAt7PT_GhpKLWkMQkxLJfy1vPqjk0,272
74
+ sud_cli-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sud-cli = sud_cli.cli:main
@@ -0,0 +1,5 @@
1
+ # Authors
2
+
3
+ sud-cli is maintained by the project owner.
4
+
5
+ Copyright (C) 2026 lnfnt <lnfnt@proton.me>