bundleclaw 0.1.0__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.
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: bundleclaw
3
+ Version: 0.1.0
4
+ Summary: Migrate OpenClaw agent state between machines
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: typer>=0.12.0
8
+ Requires-Dist: cryptography>=43.0.0
9
+
10
+ <div align="center">
11
+
12
+ # BundleClaw — Python CLI
13
+
14
+ [![PyPI version](https://img.shields.io/pypi/v/bundleclaw?logo=pypi&logoColor=white&color=3775A9)](https://pypi.org/project/bundleclaw/)
15
+ [![Python](https://img.shields.io/badge/Python-%E2%89%A53.10-3776AB?logo=python&logoColor=white)](https://www.python.org/)
16
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](../LICENSE)
17
+
18
+ Python implementation of the BundleClaw migration CLI.
19
+
20
+ </div>
21
+
22
+ ---
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ # pip
28
+ pip install bundleclaw
29
+
30
+ # uv
31
+ uv pip install bundleclaw
32
+
33
+ # Or run directly
34
+ uvx bundleclaw --help
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ ```bash
40
+ # Export agent state
41
+ bundleclaw export \
42
+ --source ~/.openclaw \
43
+ --workspace ~/.openclaw/workspace \
44
+ --profile full \
45
+ --encrypt-pass 'strong-passphrase' \
46
+ --out agent-state.bcz
47
+
48
+ # Import on target machine
49
+ bundleclaw import \
50
+ --bundle agent-state.bcz \
51
+ --target ~/.openclaw \
52
+ --encrypt-pass 'strong-passphrase'
53
+
54
+ # Verify integrity
55
+ bundleclaw verify --target ~/.openclaw
56
+
57
+ # Transfer via SCP
58
+ bundleclaw transfer \
59
+ --bundle agent-state.bcz \
60
+ --to user@host:/tmp/agent-state.bcz
61
+
62
+ # Full bootstrap (import + verify + restart + health check)
63
+ bundleclaw bootstrap \
64
+ --bundle agent-state.bcz \
65
+ --encrypt-pass 'strong-passphrase' \
66
+ --target ~/.openclaw
67
+ ```
68
+
69
+ ## Development
70
+
71
+ ```bash
72
+ python -m venv .venv
73
+ source .venv/bin/activate
74
+ pip install -e .
75
+ bundleclaw --help
76
+ ```
77
+
78
+ ### Tech Stack
79
+
80
+ - **Typer** for CLI argument parsing (Click-based)
81
+ - **cryptography** for AES-256-GCM encryption
82
+ - **zipfile** (stdlib) for ZIP archive handling
83
+ - **hashlib** (stdlib) for SHA-256 checksums
84
+
85
+ ## Interoperability
86
+
87
+ Bundles created with this CLI are fully compatible with the [Node CLI](../node-cli/). The shared `.bcz` format is documented in [`spec/FORMAT.md`](../spec/FORMAT.md).
88
+
89
+ ## License
90
+
91
+ [MIT](../LICENSE)
@@ -0,0 +1,82 @@
1
+ <div align="center">
2
+
3
+ # BundleClaw — Python CLI
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/bundleclaw?logo=pypi&logoColor=white&color=3775A9)](https://pypi.org/project/bundleclaw/)
6
+ [![Python](https://img.shields.io/badge/Python-%E2%89%A53.10-3776AB?logo=python&logoColor=white)](https://www.python.org/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](../LICENSE)
8
+
9
+ Python implementation of the BundleClaw migration CLI.
10
+
11
+ </div>
12
+
13
+ ---
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ # pip
19
+ pip install bundleclaw
20
+
21
+ # uv
22
+ uv pip install bundleclaw
23
+
24
+ # Or run directly
25
+ uvx bundleclaw --help
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ```bash
31
+ # Export agent state
32
+ bundleclaw export \
33
+ --source ~/.openclaw \
34
+ --workspace ~/.openclaw/workspace \
35
+ --profile full \
36
+ --encrypt-pass 'strong-passphrase' \
37
+ --out agent-state.bcz
38
+
39
+ # Import on target machine
40
+ bundleclaw import \
41
+ --bundle agent-state.bcz \
42
+ --target ~/.openclaw \
43
+ --encrypt-pass 'strong-passphrase'
44
+
45
+ # Verify integrity
46
+ bundleclaw verify --target ~/.openclaw
47
+
48
+ # Transfer via SCP
49
+ bundleclaw transfer \
50
+ --bundle agent-state.bcz \
51
+ --to user@host:/tmp/agent-state.bcz
52
+
53
+ # Full bootstrap (import + verify + restart + health check)
54
+ bundleclaw bootstrap \
55
+ --bundle agent-state.bcz \
56
+ --encrypt-pass 'strong-passphrase' \
57
+ --target ~/.openclaw
58
+ ```
59
+
60
+ ## Development
61
+
62
+ ```bash
63
+ python -m venv .venv
64
+ source .venv/bin/activate
65
+ pip install -e .
66
+ bundleclaw --help
67
+ ```
68
+
69
+ ### Tech Stack
70
+
71
+ - **Typer** for CLI argument parsing (Click-based)
72
+ - **cryptography** for AES-256-GCM encryption
73
+ - **zipfile** (stdlib) for ZIP archive handling
74
+ - **hashlib** (stdlib) for SHA-256 checksums
75
+
76
+ ## Interoperability
77
+
78
+ Bundles created with this CLI are fully compatible with the [Node CLI](../node-cli/). The shared `.bcz` format is documented in [`spec/FORMAT.md`](../spec/FORMAT.md).
79
+
80
+ ## License
81
+
82
+ [MIT](../LICENSE)
@@ -0,0 +1,2 @@
1
+ __all__ = ["__version__"]
2
+ __version__ = "0.1.0"
@@ -0,0 +1,295 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import shutil
7
+ import subprocess
8
+ import time
9
+ import zipfile
10
+ from pathlib import Path
11
+
12
+ import typer
13
+
14
+ app = typer.Typer(help="OpenClaw state migration tool")
15
+ ENC_MAGIC = b"BCLAWENC1"
16
+
17
+ CORE_WORKSPACE_FILES = [
18
+ "AGENTS.md",
19
+ "SOUL.md",
20
+ "USER.md",
21
+ "TOOLS.md",
22
+ "IDENTITY.md",
23
+ "MEMORY.md",
24
+ "HEARTBEAT.md",
25
+ ]
26
+
27
+
28
+ def copy_if_exists(src: Path, dst: Path) -> None:
29
+ if not src.exists():
30
+ return
31
+ dst.parent.mkdir(parents=True, exist_ok=True)
32
+ if src.is_dir():
33
+ shutil.copytree(src, dst, dirs_exist_ok=True)
34
+ else:
35
+ shutil.copy2(src, dst)
36
+
37
+
38
+ def file_sha256(path: Path) -> str:
39
+ h = hashlib.sha256()
40
+ h.update(path.read_bytes())
41
+ return f"sha256:{h.hexdigest()}"
42
+
43
+
44
+ def _crypto():
45
+ try:
46
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM # type: ignore
47
+ from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC # type: ignore
48
+ from cryptography.hazmat.primitives import hashes # type: ignore
49
+ except Exception as e: # pragma: no cover
50
+ raise RuntimeError("Encryption requested, install dependency: pip install cryptography") from e
51
+ return AESGCM, PBKDF2HMAC, hashes
52
+
53
+
54
+ def encrypt_bytes(data: bytes, passphrase: str) -> bytes:
55
+ AESGCM, PBKDF2HMAC, hashes = _crypto()
56
+ salt = os.urandom(16)
57
+ iv = os.urandom(12)
58
+ kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=200_000)
59
+ key = kdf.derive(passphrase.encode("utf-8"))
60
+ cipher = AESGCM(key)
61
+ encrypted = cipher.encrypt(iv, data, associated_data=None)
62
+ # cryptography AESGCM output = ciphertext || 16-byte tag
63
+ ciphertext, tag = encrypted[:-16], encrypted[-16:]
64
+ return ENC_MAGIC + salt + iv + tag + ciphertext
65
+
66
+
67
+ def decrypt_bytes(data: bytes, passphrase: str) -> bytes:
68
+ if not data.startswith(ENC_MAGIC):
69
+ return data
70
+ AESGCM, PBKDF2HMAC, hashes = _crypto()
71
+ salt = data[len(ENC_MAGIC): len(ENC_MAGIC) + 16]
72
+ iv = data[len(ENC_MAGIC) + 16: len(ENC_MAGIC) + 28]
73
+ tag = data[len(ENC_MAGIC) + 28: len(ENC_MAGIC) + 44]
74
+ ciphertext = data[len(ENC_MAGIC) + 44:]
75
+ kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=200_000)
76
+ key = kdf.derive(passphrase.encode("utf-8"))
77
+ cipher = AESGCM(key)
78
+ return cipher.decrypt(iv, ciphertext + tag, associated_data=None)
79
+
80
+
81
+ @app.command("export")
82
+ def export_cmd(
83
+ source: Path = typer.Option(..., help="OpenClaw home path"),
84
+ workspace: Path = typer.Option(..., help="Workspace path"),
85
+ out: Path = typer.Option(..., help="Output .bcz path"),
86
+ profile: str = typer.Option("full", help="full|memory-only|no-credentials"),
87
+ encrypt_pass: str | None = typer.Option(None, "--encrypt-pass", help="Encrypt output bundle with passphrase"),
88
+ ):
89
+ source = source.expanduser().resolve()
90
+ workspace = workspace.expanduser().resolve()
91
+ out = out.expanduser().resolve()
92
+
93
+ tmp = Path.cwd() / f"bundleclaw-export-{int(time.time())}"
94
+ payload = tmp / "payload"
95
+ payload.mkdir(parents=True, exist_ok=True)
96
+
97
+ if profile not in {"full", "memory-only", "no-credentials"}:
98
+ raise typer.BadParameter("profile must be one of: full, memory-only, no-credentials")
99
+
100
+ include_openclaw = profile != "memory-only"
101
+ include_credentials = profile == "full"
102
+ include_identity = profile == "full"
103
+ include_workspace_core = True
104
+ include_workspace_memory = True
105
+ include_workspace_config = profile != "memory-only"
106
+
107
+ if include_openclaw:
108
+ copy_if_exists(source / "openclaw.json", payload / "openclaw.json")
109
+ if include_credentials:
110
+ copy_if_exists(source / "credentials", payload / "credentials")
111
+ if include_identity:
112
+ copy_if_exists(source / "identity", payload / "identity")
113
+
114
+ ws_dst = payload / "workspace"
115
+ ws_dst.mkdir(parents=True, exist_ok=True)
116
+ if include_workspace_core:
117
+ for f in CORE_WORKSPACE_FILES:
118
+ copy_if_exists(workspace / f, ws_dst / f)
119
+ if include_workspace_memory:
120
+ copy_if_exists(workspace / "memory", ws_dst / "memory")
121
+ if include_workspace_config:
122
+ copy_if_exists(workspace / "config", ws_dst / "config")
123
+
124
+ checksums: dict[str, str] = {}
125
+ openclaw_json = payload / "openclaw.json"
126
+ if openclaw_json.exists():
127
+ checksums["payload/openclaw.json"] = file_sha256(openclaw_json)
128
+
129
+ manifest = {
130
+ "format": "bundleclaw.v1",
131
+ "createdAt": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
132
+ "encrypted": bool(encrypt_pass),
133
+ "source": {"openclawHome": str(source), "workspace": str(workspace)},
134
+ "includes": {
135
+ "openclawJson": openclaw_json.exists(),
136
+ "credentials": (payload / "credentials").exists(),
137
+ "identity": (payload / "identity").exists(),
138
+ "workspaceCore": include_workspace_core,
139
+ "workspaceMemory": (ws_dst / "memory").exists(),
140
+ "workspaceConfig": (ws_dst / "config").exists(),
141
+ },
142
+ "checksums": checksums,
143
+ }
144
+
145
+ (tmp / "manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
146
+ out.parent.mkdir(parents=True, exist_ok=True)
147
+
148
+ tmp_zip = out.with_suffix(out.suffix + ".tmpzip")
149
+ with zipfile.ZipFile(tmp_zip, "w", compression=zipfile.ZIP_DEFLATED) as zf:
150
+ zf.write(tmp / "manifest.json", arcname="manifest.json")
151
+ for p in payload.rglob("*"):
152
+ if p.is_file():
153
+ zf.write(p, arcname=str(p.relative_to(tmp)))
154
+
155
+ data = tmp_zip.read_bytes()
156
+ tmp_zip.unlink(missing_ok=True)
157
+ if encrypt_pass:
158
+ data = encrypt_bytes(data, encrypt_pass)
159
+ out.write_bytes(data)
160
+
161
+ shutil.rmtree(tmp, ignore_errors=True)
162
+ typer.echo(f"Created {out}")
163
+
164
+
165
+ @app.command("import")
166
+ def import_cmd(
167
+ bundle: Path = typer.Option(..., help="Input .bcz bundle"),
168
+ target: Path = typer.Option(..., help="Target ~/.openclaw path"),
169
+ encrypt_pass: str | None = typer.Option(None, "--encrypt-pass", help="Passphrase for encrypted bundles"),
170
+ ):
171
+ bundle = bundle.expanduser().resolve()
172
+ target = target.expanduser().resolve()
173
+
174
+ tmp = Path.cwd() / f"bundleclaw-import-{int(time.time())}"
175
+ tmp.mkdir(parents=True, exist_ok=True)
176
+
177
+ raw = bundle.read_bytes()
178
+ if raw.startswith(ENC_MAGIC):
179
+ if not encrypt_pass:
180
+ raise typer.BadParameter("Bundle is encrypted; provide --encrypt-pass")
181
+ raw = decrypt_bytes(raw, encrypt_pass)
182
+
183
+ zip_path = tmp / "bundle.zip"
184
+ zip_path.write_bytes(raw)
185
+ with zipfile.ZipFile(zip_path, "r") as zf:
186
+ zf.extractall(tmp)
187
+
188
+ payload = tmp / "payload"
189
+ if target.exists():
190
+ backup = target.parent / f"{target.name}.bundleclaw-backup-{int(time.time())}"
191
+ shutil.copytree(target, backup, dirs_exist_ok=True)
192
+ typer.echo(f"Backup created: {backup}")
193
+
194
+ target.mkdir(parents=True, exist_ok=True)
195
+ copy_if_exists(payload / "openclaw.json", target / "openclaw.json")
196
+ copy_if_exists(payload / "credentials", target / "credentials")
197
+ copy_if_exists(payload / "identity", target / "identity")
198
+ copy_if_exists(payload / "workspace", target / "workspace")
199
+
200
+ shutil.rmtree(tmp, ignore_errors=True)
201
+ typer.echo(f"Imported into {target}")
202
+
203
+
204
+ @app.command("verify")
205
+ def verify_cmd(target: Path = typer.Option(..., help="Target ~/.openclaw path")):
206
+ target = target.expanduser().resolve()
207
+ checks = [
208
+ ("openclaw.json", (target / "openclaw.json").exists()),
209
+ ("workspace/SOUL.md", (target / "workspace" / "SOUL.md").exists()),
210
+ ("workspace/memory", (target / "workspace" / "memory").exists()),
211
+ ]
212
+ failed = False
213
+ for name, ok in checks:
214
+ typer.echo(f"{'OK' if ok else 'MISSING'} {name}")
215
+ if not ok:
216
+ failed = True
217
+ raise typer.Exit(code=1 if failed else 0)
218
+
219
+
220
+ @app.command("transfer")
221
+ def transfer_cmd(
222
+ bundle: Path = typer.Option(..., help="Bundle file to transfer"),
223
+ to: str = typer.Option(..., help="scp destination, e.g. user@host:/tmp/agent-state.bcz"),
224
+ scp_bin: str = typer.Option("scp", help="scp binary"),
225
+ ):
226
+ bundle = bundle.expanduser().resolve()
227
+ subprocess.run([scp_bin, str(bundle), to], check=True)
228
+ typer.echo("Transfer complete")
229
+
230
+
231
+ @app.command("bootstrap")
232
+ def bootstrap_cmd(
233
+ bundle: Path = typer.Option(..., help="Input .bcz bundle"),
234
+ target: Path = typer.Option(..., help="Target ~/.openclaw path"),
235
+ encrypt_pass: str | None = typer.Option(None, "--encrypt-pass", help="Passphrase for encrypted bundles"),
236
+ restart_cmd: str = typer.Option("openclaw gateway restart", help="Restart command"),
237
+ skip_restart: bool = typer.Option(False, help="Skip gateway restart"),
238
+ ):
239
+ bundle = bundle.expanduser().resolve()
240
+ target = target.expanduser().resolve()
241
+
242
+ tmp = Path.cwd() / f"bundleclaw-bootstrap-{int(time.time())}"
243
+ tmp.mkdir(parents=True, exist_ok=True)
244
+
245
+ raw = bundle.read_bytes()
246
+ if raw.startswith(ENC_MAGIC):
247
+ if not encrypt_pass:
248
+ raise typer.BadParameter("Bundle is encrypted; provide --encrypt-pass")
249
+ raw = decrypt_bytes(raw, encrypt_pass)
250
+
251
+ zip_path = tmp / "bundle.zip"
252
+ zip_path.write_bytes(raw)
253
+ with zipfile.ZipFile(zip_path, "r") as zf:
254
+ zf.extractall(tmp)
255
+
256
+ payload = tmp / "payload"
257
+ if target.exists():
258
+ backup = target.parent / f"{target.name}.bundleclaw-backup-{int(time.time())}"
259
+ shutil.copytree(target, backup, dirs_exist_ok=True)
260
+ typer.echo(f"Backup created: {backup}")
261
+
262
+ target.mkdir(parents=True, exist_ok=True)
263
+ copy_if_exists(payload / "openclaw.json", target / "openclaw.json")
264
+ copy_if_exists(payload / "credentials", target / "credentials")
265
+ copy_if_exists(payload / "identity", target / "identity")
266
+ copy_if_exists(payload / "workspace", target / "workspace")
267
+
268
+ shutil.rmtree(tmp, ignore_errors=True)
269
+ typer.echo(f"Imported into {target}")
270
+
271
+ checks = [
272
+ ("openclaw.json", (target / "openclaw.json").exists()),
273
+ ("workspace/SOUL.md", (target / "workspace" / "SOUL.md").exists()),
274
+ ("workspace/memory", (target / "workspace" / "memory").exists()),
275
+ ]
276
+ for name, ok in checks:
277
+ typer.echo(f"{'OK' if ok else 'MISSING'} {name}")
278
+
279
+ if not skip_restart:
280
+ try:
281
+ subprocess.run(restart_cmd, shell=True, check=True)
282
+ except Exception:
283
+ typer.echo(f"WARN restart failed; run manually: {restart_cmd}")
284
+
285
+ for cmd in ["openclaw doctor --non-interactive", "openclaw status"]:
286
+ try:
287
+ subprocess.run(cmd, shell=True, check=True)
288
+ except Exception:
289
+ typer.echo(f"WARN command failed; run manually: {cmd}")
290
+
291
+ typer.echo("Bootstrap complete")
292
+
293
+
294
+ if __name__ == "__main__":
295
+ app()
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: bundleclaw
3
+ Version: 0.1.0
4
+ Summary: Migrate OpenClaw agent state between machines
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: typer>=0.12.0
8
+ Requires-Dist: cryptography>=43.0.0
9
+
10
+ <div align="center">
11
+
12
+ # BundleClaw — Python CLI
13
+
14
+ [![PyPI version](https://img.shields.io/pypi/v/bundleclaw?logo=pypi&logoColor=white&color=3775A9)](https://pypi.org/project/bundleclaw/)
15
+ [![Python](https://img.shields.io/badge/Python-%E2%89%A53.10-3776AB?logo=python&logoColor=white)](https://www.python.org/)
16
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](../LICENSE)
17
+
18
+ Python implementation of the BundleClaw migration CLI.
19
+
20
+ </div>
21
+
22
+ ---
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ # pip
28
+ pip install bundleclaw
29
+
30
+ # uv
31
+ uv pip install bundleclaw
32
+
33
+ # Or run directly
34
+ uvx bundleclaw --help
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ ```bash
40
+ # Export agent state
41
+ bundleclaw export \
42
+ --source ~/.openclaw \
43
+ --workspace ~/.openclaw/workspace \
44
+ --profile full \
45
+ --encrypt-pass 'strong-passphrase' \
46
+ --out agent-state.bcz
47
+
48
+ # Import on target machine
49
+ bundleclaw import \
50
+ --bundle agent-state.bcz \
51
+ --target ~/.openclaw \
52
+ --encrypt-pass 'strong-passphrase'
53
+
54
+ # Verify integrity
55
+ bundleclaw verify --target ~/.openclaw
56
+
57
+ # Transfer via SCP
58
+ bundleclaw transfer \
59
+ --bundle agent-state.bcz \
60
+ --to user@host:/tmp/agent-state.bcz
61
+
62
+ # Full bootstrap (import + verify + restart + health check)
63
+ bundleclaw bootstrap \
64
+ --bundle agent-state.bcz \
65
+ --encrypt-pass 'strong-passphrase' \
66
+ --target ~/.openclaw
67
+ ```
68
+
69
+ ## Development
70
+
71
+ ```bash
72
+ python -m venv .venv
73
+ source .venv/bin/activate
74
+ pip install -e .
75
+ bundleclaw --help
76
+ ```
77
+
78
+ ### Tech Stack
79
+
80
+ - **Typer** for CLI argument parsing (Click-based)
81
+ - **cryptography** for AES-256-GCM encryption
82
+ - **zipfile** (stdlib) for ZIP archive handling
83
+ - **hashlib** (stdlib) for SHA-256 checksums
84
+
85
+ ## Interoperability
86
+
87
+ Bundles created with this CLI are fully compatible with the [Node CLI](../node-cli/). The shared `.bcz` format is documented in [`spec/FORMAT.md`](../spec/FORMAT.md).
88
+
89
+ ## License
90
+
91
+ [MIT](../LICENSE)
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ bundleclaw/__init__.py
4
+ bundleclaw/cli.py
5
+ bundleclaw.egg-info/PKG-INFO
6
+ bundleclaw.egg-info/SOURCES.txt
7
+ bundleclaw.egg-info/dependency_links.txt
8
+ bundleclaw.egg-info/entry_points.txt
9
+ bundleclaw.egg-info/requires.txt
10
+ bundleclaw.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ bundleclaw = bundleclaw.cli:app
@@ -0,0 +1,2 @@
1
+ typer>=0.12.0
2
+ cryptography>=43.0.0
@@ -0,0 +1 @@
1
+ bundleclaw
@@ -0,0 +1,17 @@
1
+ [project]
2
+ name = "bundleclaw"
3
+ version = "0.1.0"
4
+ description = "Migrate OpenClaw agent state between machines"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "typer>=0.12.0",
9
+ "cryptography>=43.0.0"
10
+ ]
11
+
12
+ [project.scripts]
13
+ bundleclaw = "bundleclaw.cli:app"
14
+
15
+ [build-system]
16
+ requires = ["setuptools>=68", "wheel"]
17
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+