shellbrain 0.1.0__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.
- app/__init__.py +1 -0
- app/__main__.py +7 -0
- app/boot/__init__.py +1 -0
- app/boot/admin_db.py +88 -0
- app/boot/config.py +14 -0
- app/boot/create_policy.py +52 -0
- app/boot/db.py +70 -0
- app/boot/embeddings.py +55 -0
- app/boot/home.py +45 -0
- app/boot/migrations.py +61 -0
- app/boot/read_policy.py +179 -0
- app/boot/repos.py +15 -0
- app/boot/retrieval.py +3 -0
- app/boot/thresholds.py +19 -0
- app/boot/update_policy.py +34 -0
- app/boot/use_cases.py +22 -0
- app/config/__init__.py +1 -0
- app/config/defaults/create_policy.yaml +7 -0
- app/config/defaults/read_policy.yaml +25 -0
- app/config/defaults/runtime.yaml +10 -0
- app/config/defaults/thresholds.yaml +3 -0
- app/config/defaults/update_policy.yaml +5 -0
- app/config/loader.py +58 -0
- app/core/__init__.py +1 -0
- app/core/contracts/__init__.py +1 -0
- app/core/contracts/errors.py +29 -0
- app/core/contracts/requests.py +211 -0
- app/core/contracts/responses.py +15 -0
- app/core/entities/__init__.py +1 -0
- app/core/entities/associations.py +58 -0
- app/core/entities/episodes.py +66 -0
- app/core/entities/evidence.py +29 -0
- app/core/entities/facts.py +30 -0
- app/core/entities/guidance.py +47 -0
- app/core/entities/identity.py +48 -0
- app/core/entities/memory.py +34 -0
- app/core/entities/runtime_context.py +19 -0
- app/core/entities/session_state.py +31 -0
- app/core/entities/telemetry.py +152 -0
- app/core/entities/utility.py +14 -0
- app/core/interfaces/__init__.py +1 -0
- app/core/interfaces/clock.py +12 -0
- app/core/interfaces/config.py +28 -0
- app/core/interfaces/embeddings.py +12 -0
- app/core/interfaces/idgen.py +11 -0
- app/core/interfaces/repos.py +279 -0
- app/core/interfaces/retrieval.py +20 -0
- app/core/interfaces/session_state_store.py +33 -0
- app/core/interfaces/unit_of_work.py +50 -0
- app/core/policies/__init__.py +1 -0
- app/core/policies/_shared/__init__.py +1 -0
- app/core/policies/_shared/executor.py +132 -0
- app/core/policies/_shared/side_effects.py +9 -0
- app/core/policies/create_policy/__init__.py +1 -0
- app/core/policies/create_policy/pipeline.py +96 -0
- app/core/policies/read_policy/__init__.py +1 -0
- app/core/policies/read_policy/bm25.py +114 -0
- app/core/policies/read_policy/context_pack_builder.py +140 -0
- app/core/policies/read_policy/expansion.py +132 -0
- app/core/policies/read_policy/fusion_rrf.py +34 -0
- app/core/policies/read_policy/lexical_query.py +101 -0
- app/core/policies/read_policy/pipeline.py +93 -0
- app/core/policies/read_policy/scenario_lift.py +11 -0
- app/core/policies/read_policy/scoring.py +61 -0
- app/core/policies/read_policy/seed_retrieval.py +54 -0
- app/core/policies/read_policy/utility_prior.py +11 -0
- app/core/policies/update_policy/__init__.py +1 -0
- app/core/policies/update_policy/pipeline.py +80 -0
- app/core/use_cases/__init__.py +1 -0
- app/core/use_cases/build_guidance.py +85 -0
- app/core/use_cases/create_memory.py +26 -0
- app/core/use_cases/manage_session_state.py +159 -0
- app/core/use_cases/read_memory.py +21 -0
- app/core/use_cases/record_episode_sync_telemetry.py +19 -0
- app/core/use_cases/record_operation_telemetry.py +32 -0
- app/core/use_cases/sync_episode.py +162 -0
- app/core/use_cases/update_memory.py +40 -0
- app/migrations/__init__.py +1 -0
- app/migrations/env.py +65 -0
- app/migrations/versions/20260226_0001_initial_schema.py +232 -0
- app/migrations/versions/20260312_0002_add_hard_invariants.py +60 -0
- app/migrations/versions/20260312_0003_drop_create_confidence.py +40 -0
- app/migrations/versions/20260313_0004_episode_sync_hardening.py +71 -0
- app/migrations/versions/20260313_0005_evidence_episode_event_refs.py +45 -0
- app/migrations/versions/20260318_0006_usage_telemetry_schema.py +175 -0
- app/migrations/versions/20260319_0007_identity_session_guidance.py +49 -0
- app/migrations/versions/20260320_0008_instance_metadata_and_backup_safety.py +31 -0
- app/migrations/versions/__init__.py +1 -0
- app/periphery/__init__.py +1 -0
- app/periphery/admin/__init__.py +1 -0
- app/periphery/admin/backup.py +360 -0
- app/periphery/admin/destructive_guard.py +32 -0
- app/periphery/admin/doctor.py +192 -0
- app/periphery/admin/init.py +996 -0
- app/periphery/admin/instance_guard.py +211 -0
- app/periphery/admin/machine_state.py +354 -0
- app/periphery/admin/privileges.py +42 -0
- app/periphery/admin/repo_state.py +266 -0
- app/periphery/admin/restore.py +30 -0
- app/periphery/cli/__init__.py +1 -0
- app/periphery/cli/handlers.py +830 -0
- app/periphery/cli/hydration.py +119 -0
- app/periphery/cli/main.py +710 -0
- app/periphery/cli/presenter_json.py +10 -0
- app/periphery/cli/schema_validation.py +201 -0
- app/periphery/db/__init__.py +1 -0
- app/periphery/db/engine.py +10 -0
- app/periphery/db/models/__init__.py +1 -0
- app/periphery/db/models/associations.py +55 -0
- app/periphery/db/models/episodes.py +55 -0
- app/periphery/db/models/evidence.py +19 -0
- app/periphery/db/models/experiences.py +33 -0
- app/periphery/db/models/instance_metadata.py +17 -0
- app/periphery/db/models/memories.py +39 -0
- app/periphery/db/models/metadata.py +6 -0
- app/periphery/db/models/registry.py +18 -0
- app/periphery/db/models/telemetry.py +174 -0
- app/periphery/db/models/utility.py +19 -0
- app/periphery/db/models/views.py +154 -0
- app/periphery/db/repos/__init__.py +1 -0
- app/periphery/db/repos/relational/__init__.py +1 -0
- app/periphery/db/repos/relational/associations_repo.py +117 -0
- app/periphery/db/repos/relational/episodes_repo.py +188 -0
- app/periphery/db/repos/relational/evidence_repo.py +82 -0
- app/periphery/db/repos/relational/experiences_repo.py +41 -0
- app/periphery/db/repos/relational/memories_repo.py +99 -0
- app/periphery/db/repos/relational/read_policy_repo.py +202 -0
- app/periphery/db/repos/relational/telemetry_repo.py +161 -0
- app/periphery/db/repos/relational/utility_repo.py +30 -0
- app/periphery/db/repos/semantic/__init__.py +1 -0
- app/periphery/db/repos/semantic/keyword_retrieval_repo.py +63 -0
- app/periphery/db/repos/semantic/semantic_retrieval_repo.py +111 -0
- app/periphery/db/session.py +10 -0
- app/periphery/db/uow.py +75 -0
- app/periphery/embeddings/__init__.py +1 -0
- app/periphery/embeddings/local_provider.py +35 -0
- app/periphery/embeddings/query_vector_search.py +18 -0
- app/periphery/episodes/__init__.py +1 -0
- app/periphery/episodes/claude_code.py +387 -0
- app/periphery/episodes/codex.py +423 -0
- app/periphery/episodes/launcher.py +66 -0
- app/periphery/episodes/normalization.py +31 -0
- app/periphery/episodes/poller.py +299 -0
- app/periphery/episodes/source_discovery.py +66 -0
- app/periphery/episodes/tool_filter.py +165 -0
- app/periphery/identity/__init__.py +1 -0
- app/periphery/identity/claude_hook_install.py +67 -0
- app/periphery/identity/claude_runtime.py +83 -0
- app/periphery/identity/codex_runtime.py +32 -0
- app/periphery/identity/compatibility.py +38 -0
- app/periphery/identity/resolver.py +163 -0
- app/periphery/session_state/__init__.py +1 -0
- app/periphery/session_state/file_store.py +100 -0
- app/periphery/telemetry/__init__.py +33 -0
- app/periphery/telemetry/operation_summary.py +299 -0
- app/periphery/telemetry/session_selection.py +156 -0
- app/periphery/telemetry/sync_summary.py +65 -0
- app/periphery/validation/__init__.py +1 -0
- app/periphery/validation/integrity_validation.py +253 -0
- app/periphery/validation/semantic_validation.py +94 -0
- shellbrain-0.1.0.dist-info/METADATA +130 -0
- shellbrain-0.1.0.dist-info/RECORD +165 -0
- shellbrain-0.1.0.dist-info/WHEEL +5 -0
- shellbrain-0.1.0.dist-info/entry_points.txt +2 -0
- shellbrain-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Repo-local registration and identity helpers for Shellbrain."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import re
|
|
11
|
+
import subprocess
|
|
12
|
+
import tomllib
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
REPO_STATE_VERSION = 1
|
|
17
|
+
IDENTITY_STRENGTH_EXPLICIT = "explicit"
|
|
18
|
+
IDENTITY_STRENGTH_GIT_REMOTE = "git_remote"
|
|
19
|
+
IDENTITY_STRENGTH_WEAK_LOCAL = "weak_local"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class RepoRegistration:
|
|
24
|
+
"""Repo-local registration metadata bound to one machine instance."""
|
|
25
|
+
|
|
26
|
+
repo_state_version: int
|
|
27
|
+
repo_id: str
|
|
28
|
+
identity_strength: str
|
|
29
|
+
git_root: str | None
|
|
30
|
+
source_remote: str | None
|
|
31
|
+
registered_at: str
|
|
32
|
+
machine_instance_id: str
|
|
33
|
+
claude_status: str
|
|
34
|
+
claude_settings_path: str | None = None
|
|
35
|
+
claude_note: str | None = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class RepoIdentity:
|
|
40
|
+
"""Resolved repo identity before registration."""
|
|
41
|
+
|
|
42
|
+
repo_id: str
|
|
43
|
+
identity_strength: str
|
|
44
|
+
git_root: str | None
|
|
45
|
+
source_remote: str | None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def repo_runtime_dir(repo_root: Path) -> Path:
|
|
49
|
+
"""Return the repo-local runtime directory."""
|
|
50
|
+
|
|
51
|
+
return Path(repo_root).resolve() / ".shellbrain"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def repo_registration_path(repo_root: Path) -> Path:
|
|
55
|
+
"""Return the repo-local registration file path."""
|
|
56
|
+
|
|
57
|
+
return repo_runtime_dir(repo_root) / "repo_registration.toml"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def load_repo_registration(repo_root: Path) -> RepoRegistration | None:
|
|
61
|
+
"""Load one repo registration when present."""
|
|
62
|
+
|
|
63
|
+
path = repo_registration_path(repo_root)
|
|
64
|
+
try:
|
|
65
|
+
payload = tomllib.loads(path.read_text(encoding="utf-8"))
|
|
66
|
+
except FileNotFoundError:
|
|
67
|
+
return None
|
|
68
|
+
if not isinstance(payload, dict):
|
|
69
|
+
raise ValueError("Repo registration must be a TOML table.")
|
|
70
|
+
return RepoRegistration(
|
|
71
|
+
repo_state_version=int(payload.get("repo_state_version") or 0),
|
|
72
|
+
repo_id=_required_str(payload, "repo_id"),
|
|
73
|
+
identity_strength=_required_str(payload, "identity_strength"),
|
|
74
|
+
git_root=_optional_str(payload.get("git_root")),
|
|
75
|
+
source_remote=_optional_str(payload.get("source_remote")),
|
|
76
|
+
registered_at=_required_str(payload, "registered_at"),
|
|
77
|
+
machine_instance_id=_required_str(payload, "machine_instance_id"),
|
|
78
|
+
claude_status=_required_str(payload, "claude_status"),
|
|
79
|
+
claude_settings_path=_optional_str(payload.get("claude_settings_path")),
|
|
80
|
+
claude_note=_optional_str(payload.get("claude_note")),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def save_repo_registration(registration: RepoRegistration, repo_root: Path) -> Path:
|
|
85
|
+
"""Persist one repo registration."""
|
|
86
|
+
|
|
87
|
+
path = repo_registration_path(repo_root)
|
|
88
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
89
|
+
lines = [
|
|
90
|
+
f"repo_state_version = {registration.repo_state_version}",
|
|
91
|
+
f"repo_id = {json.dumps(registration.repo_id)}",
|
|
92
|
+
f"identity_strength = {json.dumps(registration.identity_strength)}",
|
|
93
|
+
f"git_root = {json.dumps(registration.git_root or '')}",
|
|
94
|
+
f"source_remote = {json.dumps(registration.source_remote or '')}",
|
|
95
|
+
f"registered_at = {json.dumps(registration.registered_at)}",
|
|
96
|
+
f"machine_instance_id = {json.dumps(registration.machine_instance_id)}",
|
|
97
|
+
f"claude_status = {json.dumps(registration.claude_status)}",
|
|
98
|
+
f"claude_settings_path = {json.dumps(registration.claude_settings_path or '')}",
|
|
99
|
+
f"claude_note = {json.dumps(registration.claude_note or '')}",
|
|
100
|
+
"",
|
|
101
|
+
]
|
|
102
|
+
path.write_text("\n".join(lines), encoding="utf-8")
|
|
103
|
+
return path
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def register_repo(
|
|
107
|
+
*,
|
|
108
|
+
repo_root: Path,
|
|
109
|
+
machine_instance_id: str,
|
|
110
|
+
explicit_repo_id: str | None = None,
|
|
111
|
+
claude_status: str = "not_checked",
|
|
112
|
+
claude_settings_path: str | None = None,
|
|
113
|
+
claude_note: str | None = None,
|
|
114
|
+
) -> RepoRegistration:
|
|
115
|
+
"""Resolve and persist one repo registration."""
|
|
116
|
+
|
|
117
|
+
identity = resolve_repo_identity(repo_root=repo_root, explicit_repo_id=explicit_repo_id)
|
|
118
|
+
registration = RepoRegistration(
|
|
119
|
+
repo_state_version=REPO_STATE_VERSION,
|
|
120
|
+
repo_id=identity.repo_id,
|
|
121
|
+
identity_strength=identity.identity_strength,
|
|
122
|
+
git_root=identity.git_root,
|
|
123
|
+
source_remote=identity.source_remote,
|
|
124
|
+
registered_at=datetime.now(timezone.utc).isoformat(),
|
|
125
|
+
machine_instance_id=machine_instance_id,
|
|
126
|
+
claude_status=claude_status,
|
|
127
|
+
claude_settings_path=claude_settings_path,
|
|
128
|
+
claude_note=claude_note,
|
|
129
|
+
)
|
|
130
|
+
save_repo_registration(registration, repo_root)
|
|
131
|
+
return registration
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def resolve_repo_identity(*, repo_root: Path, explicit_repo_id: str | None = None) -> RepoIdentity:
|
|
135
|
+
"""Resolve repo identity using explicit override, git remotes, or weak local fallback."""
|
|
136
|
+
|
|
137
|
+
target = Path(repo_root).resolve()
|
|
138
|
+
if explicit_repo_id:
|
|
139
|
+
git_root = resolve_git_root(target)
|
|
140
|
+
return RepoIdentity(
|
|
141
|
+
repo_id=explicit_repo_id,
|
|
142
|
+
identity_strength=IDENTITY_STRENGTH_EXPLICIT,
|
|
143
|
+
git_root=str(git_root) if git_root is not None else None,
|
|
144
|
+
source_remote=None,
|
|
145
|
+
)
|
|
146
|
+
git_root = resolve_git_root(target)
|
|
147
|
+
if git_root is not None:
|
|
148
|
+
remotes = list_git_remotes(git_root)
|
|
149
|
+
if "origin" in remotes:
|
|
150
|
+
return RepoIdentity(
|
|
151
|
+
repo_id=normalize_git_remote(remotes["origin"]),
|
|
152
|
+
identity_strength=IDENTITY_STRENGTH_GIT_REMOTE,
|
|
153
|
+
git_root=str(git_root),
|
|
154
|
+
source_remote="origin",
|
|
155
|
+
)
|
|
156
|
+
if len(remotes) == 1:
|
|
157
|
+
remote_name, remote_url = next(iter(remotes.items()))
|
|
158
|
+
return RepoIdentity(
|
|
159
|
+
repo_id=normalize_git_remote(remote_url),
|
|
160
|
+
identity_strength=IDENTITY_STRENGTH_GIT_REMOTE,
|
|
161
|
+
git_root=str(git_root),
|
|
162
|
+
source_remote=remote_name,
|
|
163
|
+
)
|
|
164
|
+
if len(remotes) > 1:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
"Multiple git remotes are configured and none is named origin. Rerun with --repo-id to choose a durable Shellbrain repo identity."
|
|
167
|
+
)
|
|
168
|
+
return RepoIdentity(
|
|
169
|
+
repo_id=f"{target.name}::{_weak_local_hash(target)}",
|
|
170
|
+
identity_strength=IDENTITY_STRENGTH_WEAK_LOCAL,
|
|
171
|
+
git_root=str(git_root) if git_root is not None else None,
|
|
172
|
+
source_remote=None,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def resolve_git_root(repo_root: Path) -> Path | None:
|
|
177
|
+
"""Return the git root when the target directory is inside one repository."""
|
|
178
|
+
|
|
179
|
+
try:
|
|
180
|
+
completed = subprocess.run(
|
|
181
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
182
|
+
cwd=repo_root,
|
|
183
|
+
capture_output=True,
|
|
184
|
+
text=True,
|
|
185
|
+
check=False,
|
|
186
|
+
)
|
|
187
|
+
except FileNotFoundError:
|
|
188
|
+
return None
|
|
189
|
+
if completed.returncode != 0:
|
|
190
|
+
return None
|
|
191
|
+
output = completed.stdout.strip()
|
|
192
|
+
if not output:
|
|
193
|
+
return None
|
|
194
|
+
return Path(output).expanduser().resolve()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def list_git_remotes(repo_root: Path) -> dict[str, str]:
|
|
198
|
+
"""Return fetch remotes keyed by remote name."""
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
completed = subprocess.run(
|
|
202
|
+
["git", "remote", "-v"],
|
|
203
|
+
cwd=repo_root,
|
|
204
|
+
capture_output=True,
|
|
205
|
+
text=True,
|
|
206
|
+
check=False,
|
|
207
|
+
)
|
|
208
|
+
except FileNotFoundError:
|
|
209
|
+
return {}
|
|
210
|
+
if completed.returncode != 0:
|
|
211
|
+
return {}
|
|
212
|
+
remotes: dict[str, str] = {}
|
|
213
|
+
for line in completed.stdout.splitlines():
|
|
214
|
+
parts = line.split()
|
|
215
|
+
if len(parts) < 3 or parts[2] != "(fetch)":
|
|
216
|
+
continue
|
|
217
|
+
remotes.setdefault(parts[0], parts[1])
|
|
218
|
+
return remotes
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def normalize_git_remote(url: str) -> str:
|
|
222
|
+
"""Normalize one git remote into a stable host/owner/repo identity."""
|
|
223
|
+
|
|
224
|
+
value = url.strip()
|
|
225
|
+
if not value:
|
|
226
|
+
raise ValueError("Git remote URL must not be empty.")
|
|
227
|
+
scp_match = re.match(r"^(?:[^@]+@)?([^:]+):(.+)$", value)
|
|
228
|
+
if scp_match and "://" not in value:
|
|
229
|
+
host = scp_match.group(1).lower()
|
|
230
|
+
path = scp_match.group(2)
|
|
231
|
+
else:
|
|
232
|
+
from urllib.parse import urlparse
|
|
233
|
+
|
|
234
|
+
parsed = urlparse(value)
|
|
235
|
+
host = (parsed.hostname or "").lower()
|
|
236
|
+
path = parsed.path.lstrip("/")
|
|
237
|
+
normalized_path = path.removesuffix(".git").strip("/")
|
|
238
|
+
if not host or not normalized_path:
|
|
239
|
+
raise ValueError(f"Unsupported git remote URL: {url}")
|
|
240
|
+
return f"{host}/{normalized_path}"
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _required_str(payload: dict[str, Any], key: str) -> str:
|
|
244
|
+
"""Return a required string field."""
|
|
245
|
+
|
|
246
|
+
value = payload.get(key)
|
|
247
|
+
if not isinstance(value, str) or not value:
|
|
248
|
+
raise ValueError(f"Repo registration field {key!r} must be a non-empty string.")
|
|
249
|
+
return value
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _optional_str(value: Any) -> str | None:
|
|
253
|
+
"""Return an optional string field."""
|
|
254
|
+
|
|
255
|
+
if value is None:
|
|
256
|
+
return None
|
|
257
|
+
if not isinstance(value, str):
|
|
258
|
+
raise ValueError("Optional repo registration fields must be strings when present.")
|
|
259
|
+
return value or None
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _weak_local_hash(path: Path) -> str:
|
|
263
|
+
"""Return one short weak-local identity suffix."""
|
|
264
|
+
|
|
265
|
+
digest = hashlib.sha256(str(path.resolve()).encode("utf-8")).hexdigest()
|
|
266
|
+
return digest[:12]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Restore helpers for Shellbrain logical backups."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from app.periphery.admin.backup import BackupManifest, restore_backup as _restore_backup
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def restore_backup(
|
|
9
|
+
*,
|
|
10
|
+
admin_dsn: str,
|
|
11
|
+
backup_root: Path,
|
|
12
|
+
target_db: str,
|
|
13
|
+
app_dsn: str | None = None,
|
|
14
|
+
backup_id: str | None = None,
|
|
15
|
+
container_name: str | None = None,
|
|
16
|
+
container_admin_user: str | None = None,
|
|
17
|
+
container_admin_password: str | None = None,
|
|
18
|
+
) -> BackupManifest:
|
|
19
|
+
"""Restore one backup into a fresh scratch database."""
|
|
20
|
+
|
|
21
|
+
return _restore_backup(
|
|
22
|
+
admin_dsn=admin_dsn,
|
|
23
|
+
backup_root=backup_root,
|
|
24
|
+
target_db=target_db,
|
|
25
|
+
app_dsn=app_dsn,
|
|
26
|
+
backup_id=backup_id,
|
|
27
|
+
container_name=container_name,
|
|
28
|
+
container_admin_user=container_admin_user,
|
|
29
|
+
container_admin_password=container_admin_password,
|
|
30
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""This package contains CLI entry points and payload adaptation helpers."""
|