offbox 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.
Files changed (72) hide show
  1. offbox/__init__.py +2 -0
  2. offbox/__main__.py +4 -0
  3. offbox/_data/host/cloud-init.yaml.j2 +15 -0
  4. offbox/_data/host/host-init.sh +230 -0
  5. offbox/_data/images/universal/Dockerfile +101 -0
  6. offbox/_data/images/universal/offbox-init +75 -0
  7. offbox/_data/images/universal/offbox-run +131 -0
  8. offbox/_data/providers/gcp/.terraform.lock.hcl +52 -0
  9. offbox/_data/providers/gcp/main.tf +67 -0
  10. offbox/_data/providers/gcp/outputs.tf +7 -0
  11. offbox/_data/providers/gcp/provider.toml +9 -0
  12. offbox/_data/providers/gcp/variables.tf +33 -0
  13. offbox/_data/providers/gcp/versions.tf +12 -0
  14. offbox/_data/providers/hetzner/.terraform.lock.hcl +51 -0
  15. offbox/_data/providers/hetzner/main.tf +32 -0
  16. offbox/_data/providers/hetzner/outputs.tf +7 -0
  17. offbox/_data/providers/hetzner/provider.toml +9 -0
  18. offbox/_data/providers/hetzner/variables.tf +28 -0
  19. offbox/_data/providers/hetzner/versions.tf +12 -0
  20. offbox/_data/providers/ssh/provider.toml +4 -0
  21. offbox/cli/__init__.py +117 -0
  22. offbox/cli/config.py +39 -0
  23. offbox/cli/env.py +109 -0
  24. offbox/cli/exec.py +50 -0
  25. offbox/cli/host.py +313 -0
  26. offbox/cli/misc.py +132 -0
  27. offbox/cli/ports.py +44 -0
  28. offbox/config.py +189 -0
  29. offbox/console.py +63 -0
  30. offbox/doctor.py +174 -0
  31. offbox/drivers/__init__.py +0 -0
  32. offbox/drivers/base.py +40 -0
  33. offbox/drivers/none.py +114 -0
  34. offbox/drivers/tofu.py +59 -0
  35. offbox/errors.py +45 -0
  36. offbox/execute.py +772 -0
  37. offbox/host/__init__.py +89 -0
  38. offbox/host/activity.py +58 -0
  39. offbox/host/auto.py +322 -0
  40. offbox/host/cloudinit.py +44 -0
  41. offbox/host/create.py +198 -0
  42. offbox/host/destroy.py +151 -0
  43. offbox/host/init.py +196 -0
  44. offbox/host/ready.py +106 -0
  45. offbox/host/timer.py +227 -0
  46. offbox/host/usage.py +123 -0
  47. offbox/host/warnings.py +61 -0
  48. offbox/locking.py +27 -0
  49. offbox/logging.py +154 -0
  50. offbox/logs.py +135 -0
  51. offbox/naming.py +67 -0
  52. offbox/paths.py +55 -0
  53. offbox/ports.py +610 -0
  54. offbox/project.py +187 -0
  55. offbox/providers.py +117 -0
  56. offbox/py.typed +0 -0
  57. offbox/runner.py +291 -0
  58. offbox/runtime.py +887 -0
  59. offbox/setup.py +181 -0
  60. offbox/share.py +286 -0
  61. offbox/ssh.py +179 -0
  62. offbox/state.py +157 -0
  63. offbox/status.py +378 -0
  64. offbox/sync.py +380 -0
  65. offbox/timings.py +69 -0
  66. offbox/tools.py +227 -0
  67. offbox/uninstall.py +114 -0
  68. offbox-0.1.0.dist-info/METADATA +140 -0
  69. offbox-0.1.0.dist-info/RECORD +72 -0
  70. offbox-0.1.0.dist-info/WHEEL +4 -0
  71. offbox-0.1.0.dist-info/entry_points.txt +2 -0
  72. offbox-0.1.0.dist-info/licenses/LICENSE +21 -0
offbox/uninstall.py ADDED
@@ -0,0 +1,114 @@
1
+ import logging
2
+ import os
3
+ import shutil
4
+ import stat
5
+ from contextlib import ExitStack
6
+ from pathlib import Path
7
+
8
+ from offbox import console
9
+ from offbox.errors import OffboxError, PreconditionError
10
+ from offbox.host import host_operation
11
+ from offbox.host.timer import remove as remove_reaper
12
+ from offbox.logging import close_logs
13
+ from offbox.paths import Paths
14
+ from offbox.providers import load_provider
15
+ from offbox.runner import Runner
16
+ from offbox.ssh import close_master, remove_include
17
+ from offbox.state import HostState, StateStore
18
+ from offbox.sync import SyncManager
19
+ from offbox.tools import cached_mutagen
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ def _validate_owned_directory(path: Path, config: Path) -> None:
25
+ if path.resolve().is_relative_to(config.resolve()) or config.resolve().is_relative_to(path.resolve()):
26
+ raise PreconditionError(
27
+ f"Cleanup directory overlaps user configuration: {path}", "Separate offbox config, state, cache and control directories."
28
+ )
29
+ try:
30
+ metadata = path.lstat()
31
+ except FileNotFoundError:
32
+ return
33
+ except OSError as exc:
34
+ raise PreconditionError(f"Cannot inspect cleanup directory {path}", "Check local directory permissions.") from exc
35
+ if not stat.S_ISDIR(metadata.st_mode) or metadata.st_uid != os.getuid():
36
+ raise PreconditionError(f"Unsafe cleanup directory {path}", "Restore an owned directory without symlinks before uninstalling.")
37
+
38
+
39
+ def _is_cloud(host: HostState) -> bool:
40
+ if host.provider_dir is not None:
41
+ try:
42
+ return load_provider(host.provider_dir).manifest.driver != "none"
43
+ except OffboxError:
44
+ logger.warning("cannot read provider workspace for host %s", host.name, exc_info=True)
45
+ return host.provider != "ssh"
46
+
47
+
48
+ def _describe(host: HostState) -> str:
49
+ return f"{host.name} ({host.provider}, {host.address or 'no address yet'}, label offbox-host={host.name})"
50
+
51
+
52
+ def _stop_cached_mutagen_daemon(paths: Paths, runner: Runner) -> None:
53
+ executable = cached_mutagen(paths.cache)
54
+ if executable is None or shutil.which("mutagen") is not None:
55
+ return
56
+ result = runner.run([str(executable), "daemon", "stop"], check=False, timeout=30)
57
+ if result.returncode != 0:
58
+ logger.warning("cannot stop the downloaded Mutagen daemon: %s", result.stderr.strip())
59
+
60
+
61
+ def uninstall(
62
+ paths: Paths, runner: Runner, *, yes: bool = False, abandon_cloud_hosts: bool = False, ssh_config: Path | None = None
63
+ ) -> None:
64
+ for path in (paths.state, paths.cache, paths.control):
65
+ _validate_owned_directory(path, paths.config)
66
+ if paths.config.is_symlink():
67
+ raise PreconditionError("Offbox config directory is a symlink", "Restore the owned config directory before uninstalling.")
68
+ state = StateStore(paths.state).read()
69
+ if state.hosts:
70
+ names = ", ".join(sorted(state.hosts))
71
+ fix = "; ".join(f"offbox host destroy {name}" for name in sorted(state.hosts))
72
+ cloud = [host for _, host in sorted(state.hosts.items()) if _is_cloud(host)]
73
+ retain = "offbox uninstall --yes --abandon-cloud-hosts" if cloud else "offbox uninstall --yes"
74
+ if not yes:
75
+ raise PreconditionError(
76
+ f"Hosts remain: {names}. Uninstall does not destroy remote hosts.",
77
+ f"Run {fix} first, or {retain} to retain the hosts.",
78
+ )
79
+ if cloud and not abandon_cloud_hosts:
80
+ raise PreconditionError(
81
+ "Cloud hosts remain and keep billing after uninstall: " + "; ".join(_describe(host) for host in cloud),
82
+ "Run "
83
+ + "; ".join(f"offbox host destroy {host.name} --yes" for host in cloud)
84
+ + " first, or add --abandon-cloud-hosts to keep them running without local records.",
85
+ )
86
+ for host in cloud:
87
+ console.warn(f"Abandoning cloud host {_describe(host)}; delete it in the provider console to stop billing.")
88
+ console.warn(f"Hosts remain remotely: {names}. Their local provisioning state will be removed.")
89
+ if remove_reaper(paths, runner):
90
+ console.info("Removed the idle reaper timer")
91
+ with ExitStack() as locks:
92
+ for name in sorted(state.hosts):
93
+ locks.enter_context(host_operation(paths, name))
94
+ SyncManager(runner, cache=paths.cache).terminate_all()
95
+ for host in state.hosts.values():
96
+ if host.address:
97
+ close_master(runner, host.name)
98
+ if StateStore(paths.state).read() != state:
99
+ raise PreconditionError(
100
+ "Local state changed during uninstall", "Wait for other offbox operations to finish and retry uninstall."
101
+ )
102
+ _stop_cached_mutagen_daemon(paths, runner)
103
+ try:
104
+ close_logs()
105
+ for path in dict.fromkeys((paths.cache, paths.control, paths.state)):
106
+ if path.exists():
107
+ shutil.rmtree(path)
108
+ remove_include(paths, ssh_config)
109
+ (paths.config / "ssh_config").unlink(missing_ok=True)
110
+ except OSError as exc:
111
+ raise PreconditionError(
112
+ "Cannot remove offbox local resources", "Check directory permissions and rerun offbox uninstall --yes."
113
+ ) from exc
114
+ console.success("Removed offbox local resources; remote hosts and user configuration are retained")
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.5
2
+ Name: offbox
3
+ Version: 0.1.0
4
+ Summary: Give every git worktree its own Docker stack on one cloud VM.
5
+ Project-URL: Homepage, https://github.com/o-avdieiev/offbox
6
+ Project-URL: Repository, https://github.com/o-avdieiev/offbox
7
+ Project-URL: Documentation, https://github.com/o-avdieiev/offbox/tree/master/docs
8
+ Project-URL: Changelog, https://github.com/o-avdieiev/offbox/blob/master/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/o-avdieiev/offbox/issues
10
+ Author-email: Oleksii Avdieiev <50865612+o-avdieiev@users.noreply.github.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: coding-agents,docker,docker-compose,gcp,git-worktree,hetzner,mutagen,remote-development,ssh
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: MacOS
19
+ Classifier: Operating System :: POSIX :: Linux
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Software Development
24
+ Requires-Python: >=3.12
25
+ Requires-Dist: jinja2<4,>=3.1
26
+ Requires-Dist: pydantic<3,>=2
27
+ Requires-Dist: rich<16,>=13
28
+ Requires-Dist: typer<0.28,>=0.27
29
+ Description-Content-Type: text/markdown
30
+
31
+ # offbox
32
+
33
+ [![Test](https://github.com/o-avdieiev/offbox/actions/workflows/test.yaml/badge.svg?branch=master)](https://github.com/o-avdieiev/offbox/actions/workflows/test.yaml)
34
+ [![License: MIT](https://img.shields.io/github/license/o-avdieiev/offbox)](LICENSE)
35
+ [![Python 3.12 | 3.13](https://img.shields.io/badge/python-3.12%20%7C%203.13-blue)](pyproject.toml)
36
+
37
+ Give every git worktree its own Docker stack on one cloud VM you own. Your compose files don't change.
38
+
39
+ Running several coding agents or worktrees in parallel means several full Docker Compose stacks: databases, browsers, builds and test runs competing for one laptop's CPU, memory and ports. Offbox moves those stacks to a single VM and gives each worktree its own Docker daemon, volumes and port forwards. Your editor, agents and Git stay on the laptop.
40
+
41
+ On a Google Cloud `e2-standard-4` VM, a warm `offbox exec -- true` took a median **1.07 s** end to end, including the sync flush and dependency checks; a first `up` of the FastAPI/Postgres sample took **39 s**, and the VM cost about **$0.15/hour**. See the [Google Cloud](docs/benchmarks/2026-10-23-gcp.md) and [Hetzner](docs/benchmarks/2026-10-23-hetzner.md) benchmark reports for method and limits.
42
+
43
+ ## Quickstart
44
+
45
+ macOS, Linux, or Windows via WSL2 · git and ssh — `offbox setup` installs the rest.
46
+
47
+ ```sh
48
+ curl -LsSf https://raw.githubusercontent.com/o-avdieiev/offbox/master/install.sh | sh # installs uv + offbox, runs offbox setup
49
+ cd your-repo
50
+ offbox up
51
+ offbox exec -- pytest
52
+ offbox ports
53
+ ```
54
+
55
+ `setup` checks your tools, downloads verified Mutagen and OpenTofu when they are missing, offers a dedicated SSH key if you have none, and then creates a Hetzner or Google Cloud host in your own account or registers an existing Ubuntu 24.04 server. Cloud credentials come from your environment, never from a prompt. Creating a cloud host is billable; review its size and current price before approving. The [installation guide](docs/installation.md) walks through each platform and provider step by step, including a manual install without the script.
56
+
57
+ When finished with a worktree, use `offbox rm --yes`. To delete a cloud host and stop its server charges:
58
+
59
+ ```sh
60
+ offbox host list
61
+ offbox host destroy main --yes
62
+ ```
63
+
64
+ `offbox stop` retains the environment's data. It does not delete the server or stop cloud billing. Hetzner bills servers until deletion, including powered-off servers. See [current pricing](https://www.hetzner.com/cloud/) and [billing rules](https://docs.hetzner.com/cloud/billing/faq/).
65
+
66
+ ## How it works
67
+
68
+ ```mermaid
69
+ flowchart LR
70
+ subgraph Laptop
71
+ Editor[Editor and coding agents]
72
+ A[Git worktree A]
73
+ B[Git worktree B]
74
+ CLI[offbox CLI and local state]
75
+ end
76
+ subgraph Host[Personal SSH host]
77
+ WA[Work directory A]
78
+ WB[Work directory B]
79
+ EA[Environment A: Docker daemon + volumes]
80
+ EB[Environment B: Docker daemon + volumes]
81
+ WA --> EA
82
+ WB --> EB
83
+ end
84
+ A <-->|Mutagen: source files| WA
85
+ B <-->|Mutagen: source files| WB
86
+ CLI -->|SSH: commands and port forwards| EA
87
+ CLI -->|SSH: commands and port forwards| EB
88
+ ```
89
+
90
+ Both worktrees can publish remote port 8000 and use the same Compose container names. Offbox gives them separate Docker daemons and local forwards such as `localhost:8000` and `localhost:8001`. `offbox ports --swap 8000` exchanges the preferred local port when an app needs a fixed callback URL.
91
+
92
+ `up` creates or starts the environment, synchronizes files, installs declared dependencies, starts the declared stack and restores forwards. `exec` flushes sync, checks dependency hashes and streams the requested command with its actual exit code. Git metadata is excluded from sync; run Git locally.
93
+
94
+ Use `offbox --timings up` or `offbox --timings exec -- COMMAND` to see stage durations on stderr. Timing totals exclude Python/CLI startup; command output and exit codes are preserved.
95
+
96
+ ## Why not DevPod, Codespaces or Docker over SSH?
97
+
98
+ - **Worktree-native.** Each worktree gets its own Docker daemon, volumes and port forwards on the shared VM, so several worktrees can run the same Compose project, with the same container names and ports, at the same time.
99
+ - **Nothing to adopt.** No `devcontainer.json`, no changes to your compose files and no offbox daemon or control plane to run. An optional `offbox.toml` declares install and stack commands when the defaults are not enough.
100
+ - **Your editor, agents and Git stay local.** Mutagen keeps files in sync both ways; commands and port forwards go over SSH.
101
+ - **One VM you own.** Hetzner, Google Cloud or any Ubuntu 24.04 server you can reach over SSH. You pay the provider directly.
102
+
103
+ Use DevPod or Codespaces if you want a full remote editor environment defined by `devcontainer.json`. Use sandbox APIs such as [E2B](https://e2b.dev/docs) for untrusted code.
104
+
105
+ ## Requirements and scope
106
+
107
+ - macOS or Linux client, Python 3.12+, Git 2.30+, OpenSSH 8+ and Mutagen 0.18.x; `offbox setup` downloads a verified Mutagen 0.18.1 if none is installed. Windows clients use WSL2.
108
+ - An Ubuntu 24.04 amd64 host. The universal image is Linux amd64; an Apple Silicon laptop is supported as a client.
109
+ - OpenTofu 1.8+ or Terraform 1.6+ for cloud provisioning. If neither is installed, Offbox can download verified pinned OpenTofu.
110
+ - Trusted personal projects. Environment containers are privileged and share the host kernel; they are not a sandbox for untrusted code. Two-way sync also lets code on the VM change files in your worktree. Read the [security model](docs/security.md).
111
+
112
+ See [limits](docs/limits.md) for the full operating model.
113
+
114
+ ## FAQ
115
+
116
+ **Does it work on Apple Silicon or arm64?** An arm64 laptop works as a client. Hosts must be amd64, because the universal image is built only for `linux/amd64`; arm64 host images are not provided.
117
+
118
+ **Does an idle host stop automatically?** No. There is no automatic idle stop or destroy. `offbox stop` stops an environment but not the VM or its billing; run `offbox host destroy NAME --yes` when you are done.
119
+
120
+ **What does it cost?** Whatever your provider charges for the VM. The benchmarked Google Cloud `e2-standard-4` was about $0.15/hour plus disk and IP charges; the default Hetzner size is `ccx33`. Check current provider pricing before creating a host.
121
+
122
+ **Can I use another cloud?** The built-in providers are Hetzner, Google Cloud and existing SSH servers. Any Ubuntu 24.04 amd64 server works through `ssh`, and you can add an OpenTofu/Terraform provider; see [writing a provider](docs/writing-a-provider.md).
123
+
124
+ **Does it support Windows?** Use WSL2 as the client; see [Windows (WSL2)](docs/installation.md#windows-wsl2).
125
+
126
+ **Do I need a devcontainer?** No. Offbox runs your existing Compose files in the universal image. Set `env.image` or `env.dockerfile` in `offbox.toml` to use your own image.
127
+
128
+ ## Configuration and examples
129
+
130
+ An `offbox.toml` at the Git worktree root configures resources, dependency installation, stack commands, forwards, sync ignores, forwarded variables, copied files and the git mirror. Every key is optional, and each can also come from your user config (for every repository), an uncommitted `offbox.local.toml` or an `OFFBOX_*` variable; `offbox config` shows the result and where each value came from. The environment gets a real Git repository by default. With `auto = true` on a host preset, offbox creates the VM when a worktree first needs it and destroys it after the last one is removed or it sits idle, so a forgotten VM does not keep billing. See the [complete configuration reference](docs/configuration.md).
131
+
132
+ - [FastAPI + Postgres](examples/python-compose/): real database-backed API and tests, with ports 8000 and 5432.
133
+ - [Vite + Playwright](examples/node-playwright/): a small web app and browser test, with port 3000.
134
+ - [Agent instructions and JSON contracts](docs/agents.md).
135
+ - [Architecture and lifecycle](docs/architecture.md).
136
+ - [Installation](docs/installation.md), [providers](docs/providers.md), [writing a provider](docs/writing-a-provider.md), and [troubleshooting](docs/troubleshooting.md).
137
+
138
+ ## Contributing
139
+
140
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, checks and CI, and [SECURITY.md](SECURITY.md) for private vulnerability reporting. Offbox is not yet published to PyPI; install it from this repository as shown above. Licensed under [MIT](LICENSE).
@@ -0,0 +1,72 @@
1
+ offbox/_data/host/cloud-init.yaml.j2,sha256=HKanv9TfhoNVigsLi0SBaaLGkYDZc_nARyjhbulyiss,380
2
+ offbox/_data/host/host-init.sh,sha256=pNq9crwnnFOK7C9CIpcN9qGT1d4IVW9FvJiGHvAOQuM,9839
3
+ offbox/_data/images/universal/Dockerfile,sha256=sn9SthjeGvdsW5kViIYodMWgOaqfsrKeJmVz1cnqoGk,6401
4
+ offbox/_data/images/universal/offbox-init,sha256=VnaKl28by8VDMsuorynGuE4FXOt6mMxhVQOEE-BR19A,2606
5
+ offbox/_data/images/universal/offbox-run,sha256=U-Tz95WpL5Gt2QtW2ozj1QeG2DefnUmfWT24hPdaSD0,4819
6
+ offbox/_data/providers/gcp/.terraform.lock.hcl,sha256=z2tzPeXQkyiZV3mpBoPAuOV88y61EXkr6t7_MqmcmM4,3029
7
+ offbox/_data/providers/gcp/main.tf,sha256=rwTkSLtPRRoeCKwgCYNskcgO_xh374VNA1_uqBAYsuo,1487
8
+ offbox/_data/providers/gcp/outputs.tf,sha256=ipKUu-pKs6VJsDozKPeNbAX5osjBePaYUhzjzTCKXDw,142
9
+ offbox/_data/providers/gcp/provider.toml,sha256=BrZBg3beP6DG3kOxFLAX9C4Gso-PCJrTYp95vRobjNY,221
10
+ offbox/_data/providers/gcp/variables.tf,sha256=PWVlkoBAZHizXU7SwsWuzR0hiVxPAhCGHghlS2OGsh8,486
11
+ offbox/_data/providers/gcp/versions.tf,sha256=dqNKO6p81YkBn4pgVeeG9Ou55mvWQzZn7fSrMtvPU6o,182
12
+ offbox/_data/providers/hetzner/.terraform.lock.hcl,sha256=MbwFIdQMyIHcmTJruPMATQ6DsFGAsByuVNjJDCHmAp8,2966
13
+ offbox/_data/providers/hetzner/main.tf,sha256=J24Gz0pHbGqVhLKh3GrqLvlnqiYjZe1QEz-BE66dh00,613
14
+ offbox/_data/providers/hetzner/outputs.tf,sha256=S36Y3RbBZPbPrg7KA_8_MfCjdRtHFHJ-JfUDlBCGzR4,100
15
+ offbox/_data/providers/hetzner/provider.toml,sha256=3231K9IdsXc4hEmre7rHD9RhLGCXpHJTNSfK_Es5Ajw,176
16
+ offbox/_data/providers/hetzner/variables.tf,sha256=GnsQt0DxqUTpvVOQxkQ6pp0ypfMfoP7_OqaJ8_Om5zM,307
17
+ offbox/_data/providers/hetzner/versions.tf,sha256=IE2K4JLJmPO9i0zaEzD20bmvTUzi1MRPEHpEAmg1WsY,187
18
+ offbox/_data/providers/ssh/provider.toml,sha256=tmjCcRERtlIYPiS03HqdhzzwHVeteecItSKa7-9TbRU,107
19
+ offbox/__init__.py,sha256=uCndGMXbtIILNRoTDoorykyTWw0u2Bm-PQGb22XHozk,78
20
+ offbox/__main__.py,sha256=xOgH_sN3gNv2w8SdCgAd9039inLr53cvRvp3iazeztw,65
21
+ offbox/config.py,sha256=li1lqrGnBPfu3EUm-0ct84YRglxp02N698Lk0q11K4I,7076
22
+ offbox/console.py,sha256=djTVX4cgtStJf8XPMvkzvWqCfKCWVR2Jk43V3pE8Xzs,1250
23
+ offbox/doctor.py,sha256=kb8FlJI0DbPws5Jxo8SngiVLO0_z241AHFP6lDeONLs,8070
24
+ offbox/errors.py,sha256=c7QzKB9lnxXgO0xxGqdRInUGrN_4X7SRF_wcEKomIoI,1451
25
+ offbox/execute.py,sha256=3A9KC1uIZJ1hGSL-lN-NRkrl-RJ4L2ARtaBtSgUxXnk,33661
26
+ offbox/locking.py,sha256=LjfeOdf9wyVbibYrQ_v2zLo-eZGYpmJhrTdoFzE_71Y,944
27
+ offbox/logging.py,sha256=Ze-mlHxfyWF9MflEc2bbM6R4P5gWeo8-DR-REghwdYM,5405
28
+ offbox/logs.py,sha256=3_o-r5Jvus78aO7G8YnjazvJllAP1aBEiAwPwlOMAUQ,5351
29
+ offbox/naming.py,sha256=sJmevG5HWbYBZoesWN4ywi3lGSCdlrD7n1bbBTSVJHs,2016
30
+ offbox/paths.py,sha256=NITNMft56jxL1yzmzRtKnvZA1f4vaHlLoLHCeWcZurk,1834
31
+ offbox/ports.py,sha256=hVj1uSVg7x4eKySMwMI3nymm13ieo35CC9B7hNjg81w,30006
32
+ offbox/project.py,sha256=DTtj7XLK4WxCTLlW_Ri6ijTdoIA4ImDeAT6pFTwFoD4,7334
33
+ offbox/providers.py,sha256=crvnCK80BrBdN0ww4NQML7qrMUbIneraikzx0BEV9wE,4903
34
+ offbox/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
+ offbox/runner.py,sha256=BrXjoKJO1ZLfmyfoYxVIOpBQtXr1JFqsfB9qhqzLIeI,12699
36
+ offbox/runtime.py,sha256=LPhFvbcpjxRqhler8sJAseGqVmSzL_4JB9u-f0xizXY,42071
37
+ offbox/setup.py,sha256=OcJAjKEkN2h90f2teARCbD-PMD6L9XkXOI75oPhymeo,8081
38
+ offbox/share.py,sha256=zPuQHR39qxp88QwL801-xNLqQsUtIy_KHE6BAV7lv9U,14013
39
+ offbox/ssh.py,sha256=WhFMZKute5xbjGZR95udje8VL334RqU1Jz2rlzIZrMk,7476
40
+ offbox/state.py,sha256=4wcD-2Frdd8LRqotzHNzS02s83pqTiijaz_nXvPcpPI,5823
41
+ offbox/status.py,sha256=rZbSzR3-ioyxDeSN8Uan3eFsFZTinMW6wfAU2BI1qu8,16579
42
+ offbox/sync.py,sha256=RUS0lQbBZ82tvSwz-JPI6LVOhwQJ9ROOHUXAIDd_cbY,15553
43
+ offbox/timings.py,sha256=DWgtgbUuELCc2jtyMv9LGQdQWP-3BwXeFm6YtLtM9rc,1823
44
+ offbox/tools.py,sha256=G69CWPMvM56Pbr7fZ1-3K23ap96c2n6mBEY0ZFM3cqA,10075
45
+ offbox/uninstall.py,sha256=Ul7eW_kCxv4xKAC8XkWriOKYBHc-edkgbyMywP04wgE,5364
46
+ offbox/cli/__init__.py,sha256=iqxnLDjyQN5ev0AL4lhg8VKf7lh5sOT5SB3YSDqkpNY,4552
47
+ offbox/cli/config.py,sha256=y-jg9KZAYrQ0Yg29j3PNMxE6o_EaHB73kW8bLrj-X80,1632
48
+ offbox/cli/env.py,sha256=eoMiYxlWpbPUwTpCBa-OO5I9OKfgEhz5Nfdf2fdlaB0,4348
49
+ offbox/cli/exec.py,sha256=Kro6BX9n-OzYphm_ESgvywIDK1__RG1Y-ShgXGxJ4sA,2320
50
+ offbox/cli/host.py,sha256=uGenSAcx1tTe9j45mTIRyBxn9uTquI0UCGCko90tvR8,13883
51
+ offbox/cli/misc.py,sha256=cT4eQnpsmHerGLffcSRmJc5Hx-P7eOJVDpVzKxIf3L0,5688
52
+ offbox/cli/ports.py,sha256=86kKNlsHynV0vBPE8HWgBaql4y6jdFJynIy_0yX-eXg,1565
53
+ offbox/drivers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
+ offbox/drivers/base.py,sha256=YIOSY8D0v1cWAmFWJO5GwpzKsXxdDe3-xwpc2_80Vuo,775
55
+ offbox/drivers/none.py,sha256=UtCNHSqzbUQUe_WX7RFo977NS-tqHEjWF2mRotBm0aY,6391
56
+ offbox/drivers/tofu.py,sha256=XLUMeApkC3WREa0IyEWDOHXeLg-W0-n1lxA2NQlyQDI,2524
57
+ offbox/host/__init__.py,sha256=vaSN_H2kccqIxtR6w_77dH33ZXg2IcVijKK1Kb7UgwE,4243
58
+ offbox/host/activity.py,sha256=UHh4BYh7WHqgCp0iVMP4G-qjc0zqW_OkYJpuCAH5lws,1856
59
+ offbox/host/auto.py,sha256=Xe7H9Ms-L94q8BII1_-TV9u-9Dj4FqzhbX4ZqQiFGbc,14139
60
+ offbox/host/cloudinit.py,sha256=PLOfeI4nHgFszUMO--LT79VM0Bg4qwtOfyxmWLC0XFg,1782
61
+ offbox/host/create.py,sha256=UHeyAKbEM_n02vT_X-RN43tyEzLxITDqJl1KShZvNG4,8674
62
+ offbox/host/destroy.py,sha256=mVOWH_eRx-WdDEonvsTh9K6pXVz6dEZpNxWtdWV8nXs,7356
63
+ offbox/host/init.py,sha256=GTi44-Uh9rtqwcn0Pkb9w9VTeNTY-ioEX2RiMwH1J_0,8517
64
+ offbox/host/ready.py,sha256=H_zO8BGrPEW4QDwTfvWVH1-0eORIKNYxRTL6BTFrw9Y,3411
65
+ offbox/host/timer.py,sha256=ngOPX_nGMp8FqchsrQIIQuIwN3U1d37ipPMMcL09Ubc,10537
66
+ offbox/host/usage.py,sha256=ZhCLR430d9LuwHv9psmtBzUQ3SmODGvd5i_-U7j3QwM,4015
67
+ offbox/host/warnings.py,sha256=SQZCqJKWw5zdssR4CNZIWHeB-xP9mGOZFzNMpiKua5Q,1868
68
+ offbox-0.1.0.dist-info/METADATA,sha256=Wty2mKfkHj-u_6Thr5lgPL6O_4YLJ4XA_vIlQIIk3Bk,9699
69
+ offbox-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
70
+ offbox-0.1.0.dist-info/entry_points.txt,sha256=yE2XDjo-7se4laV1Qs1HEYeFgWDkDOWnp7Xe2exwWv8,42
71
+ offbox-0.1.0.dist-info/licenses/LICENSE,sha256=sOmgVHMB3UnFbkHVFmmRWlLD_g6A7AZmFks0M8B7pYQ,1073
72
+ offbox-0.1.0.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
+ offbox = offbox.cli:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oleksii Avdieiev
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.