agent-leash 0.3.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.
Files changed (41) hide show
  1. agent_leash-0.3.0/.github/workflows/ci.yml +21 -0
  2. agent_leash-0.3.0/.github/workflows/release.yml +75 -0
  3. agent_leash-0.3.0/.gitignore +5 -0
  4. agent_leash-0.3.0/.pre-commit-config.yaml +55 -0
  5. agent_leash-0.3.0/CONTRIBUTING.md +120 -0
  6. agent_leash-0.3.0/LICENSE +21 -0
  7. agent_leash-0.3.0/Makefile +11 -0
  8. agent_leash-0.3.0/PKG-INFO +18 -0
  9. agent_leash-0.3.0/README.md +85 -0
  10. agent_leash-0.3.0/RELEASE.md +37 -0
  11. agent_leash-0.3.0/docs/demo.gif +0 -0
  12. agent_leash-0.3.0/frontend/eslint.config.js +36 -0
  13. agent_leash-0.3.0/frontend/index.html +12 -0
  14. agent_leash-0.3.0/frontend/package-lock.json +3046 -0
  15. agent_leash-0.3.0/frontend/package.json +30 -0
  16. agent_leash-0.3.0/frontend/src/App.vue +168 -0
  17. agent_leash-0.3.0/frontend/src/components/DomainApproval.vue +38 -0
  18. agent_leash-0.3.0/frontend/src/components/DomainsPane.vue +67 -0
  19. agent_leash-0.3.0/frontend/src/components/FilesystemPane.vue +242 -0
  20. agent_leash-0.3.0/frontend/src/components/SandboxDetail.vue +56 -0
  21. agent_leash-0.3.0/frontend/src/components/ServicesPane.vue +174 -0
  22. agent_leash-0.3.0/frontend/src/components/TerminalPane.vue +159 -0
  23. agent_leash-0.3.0/frontend/src/main.ts +6 -0
  24. agent_leash-0.3.0/frontend/src/style.css +190 -0
  25. agent_leash-0.3.0/frontend/tsconfig.json +13 -0
  26. agent_leash-0.3.0/frontend/vite.config.ts +16 -0
  27. agent_leash-0.3.0/hatch_build.py +12 -0
  28. agent_leash-0.3.0/pyproject.toml +50 -0
  29. agent_leash-0.3.0/src/aleash/__init__.py +0 -0
  30. agent_leash-0.3.0/src/aleash/bwrap.py +218 -0
  31. agent_leash-0.3.0/src/aleash/cli.py +209 -0
  32. agent_leash-0.3.0/src/aleash/db.py +223 -0
  33. agent_leash-0.3.0/src/aleash/profiles.py +70 -0
  34. agent_leash-0.3.0/src/aleash/proxy_addon.py +50 -0
  35. agent_leash-0.3.0/src/aleash/runner.py +556 -0
  36. agent_leash-0.3.0/src/aleash/server.py +657 -0
  37. agent_leash-0.3.0/src/aleash/services.py +126 -0
  38. agent_leash-0.3.0/src/aleash/static/assets/index-DZWOxijS.css +32 -0
  39. agent_leash-0.3.0/src/aleash/static/assets/index-gPIKKcCv.js +25 -0
  40. agent_leash-0.3.0/src/aleash/static/index.html +13 -0
  41. agent_leash-0.3.0/uv.lock +2295 -0
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - main
10
+
11
+ jobs:
12
+ lint:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v6
16
+ - uses: astral-sh/setup-uv@v8.1.0
17
+ - name: Install frontend deps
18
+ run: npm --prefix frontend ci
19
+ - uses: pre-commit/action@v3.0.1
20
+ with:
21
+ extra_args: --all-files
@@ -0,0 +1,75 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v[0-9]+.[0-9]+.[0-9]+'
7
+
8
+ permissions:
9
+ contents: write
10
+ id-token: write
11
+
12
+ jobs:
13
+ validate:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v4
18
+ - name: Check tag matches pyproject.toml version
19
+ run: |
20
+ TAG_VERSION="${GITHUB_REF_NAME#v}"
21
+ PKG_VERSION=$(uv version --short)
22
+ if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
23
+ echo "Tag $GITHUB_REF_NAME does not match pyproject.toml version $PKG_VERSION"
24
+ exit 1
25
+ fi
26
+ lint:
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@v6
30
+ - uses: astral-sh/setup-uv@v8.1.0
31
+ - uses: pre-commit/action@v3.0.1
32
+ with:
33
+ extra_args: --all-files
34
+ build:
35
+ needs: [validate, lint]
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ - uses: astral-sh/setup-uv@v4
40
+ - name: Install frontend deps
41
+ run: npm --prefix frontend ci
42
+ - name: Build distribution
43
+ run: uv build
44
+ - uses: actions/upload-artifact@v4
45
+ with:
46
+ name: dist
47
+ path: dist/
48
+
49
+ publish-pypi:
50
+ needs: [build]
51
+ runs-on: ubuntu-latest
52
+ environment: pypi
53
+ steps:
54
+ - uses: actions/download-artifact@v4
55
+ with:
56
+ name: dist
57
+ path: dist/
58
+ - name: Publish to PyPI
59
+ uses: pypa/gh-action-pypi-publish@release/v1
60
+
61
+
62
+ github-release:
63
+ needs: [publish-pypi]
64
+ runs-on: ubuntu-latest
65
+ steps:
66
+ - uses: actions/download-artifact@v4
67
+ with:
68
+ name: dist
69
+ path: dist/
70
+ - name: Create GitHub Release
71
+ uses: softprops/action-gh-release@v2
72
+ with:
73
+ files: |
74
+ dist/*
75
+ generate_release_notes: true
@@ -0,0 +1,5 @@
1
+ **/*.swp
2
+ **/node_modules/**
3
+ **/__pycache__/**
4
+ frontend/tsconfig.tsbuildinfo
5
+ .venv/
@@ -0,0 +1,55 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v6.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-toml
9
+ - id: check-merge-conflict
10
+ - id: check-added-large-files
11
+ args: [--maxkb=1000]
12
+ - id: debug-statements
13
+ - repo: https://github.com/codespell-project/codespell
14
+ rev: v2.4.2
15
+ hooks:
16
+ - id: codespell
17
+ name: Spell check source files
18
+ - repo: https://github.com/astral-sh/ruff-pre-commit
19
+ rev: v0.15.14
20
+ hooks:
21
+ - id: ruff
22
+ args: [--fix]
23
+ - id: ruff-format
24
+ - repo: local
25
+ hooks:
26
+ - id: pyright
27
+ name: Type checking for python
28
+ entry: uv run --frozen pyright
29
+ language: system
30
+ types: [python]
31
+ pass_filenames: false
32
+ - id: vue-tsc
33
+ name: TypeScript type check (frontend)
34
+ entry: npm --prefix frontend run typecheck
35
+ language: system
36
+ files: ^frontend/src/.*\.(ts|vue)$
37
+ pass_filenames: false
38
+ - id: eslint-frontend
39
+ name: ESLint (frontend)
40
+ entry: npm --prefix frontend run lint
41
+ language: system
42
+ files: ^frontend/src/.*\.(ts|vue)$
43
+ pass_filenames: false
44
+ - id: licensecheck
45
+ name: License compatibility
46
+ entry: uv run --frozen licensecheck
47
+ language: system
48
+ pass_filenames: false
49
+ stages: [pre-push]
50
+ - id: uv-audit
51
+ name: Vulnerability scan (uv audit)
52
+ entry: uv audit
53
+ language: system
54
+ pass_filenames: false
55
+ stages: [pre-push]
@@ -0,0 +1,120 @@
1
+ # Contributing
2
+
3
+ ## System requirements
4
+
5
+ | Tool | Purpose | Install |
6
+ |------|---------|---------|
7
+ | `bwrap` | filesystem isolation | `dnf install bubblewrap` / `apt install bubblewrap` |
8
+ | `xdg-dbus-proxy` | portal socket for xdg-open | `dnf install xdg-dbus-proxy` / `apt install xdg-dbus-proxy` |
9
+ | Python ≥ 3.11 | backend | system or pyenv |
10
+ | Node ≥ 18 + npm | frontend build | system or nvm |
11
+
12
+ `mitmproxy` is a Python dependency — no separate install needed.
13
+
14
+ ## First-time setup
15
+
16
+ ```sh
17
+ git clone <repo> aleash
18
+ cd aleash
19
+
20
+ # Python env — uv creates .venv, downloads Python 3.12 (pinned in .python-version),
21
+ # and installs all deps. The frontend build is skipped for editable installs.
22
+ uv sync
23
+
24
+ # Frontend deps + initial build (one-time)
25
+ cd frontend
26
+ npm install # generates package-lock.json if missing
27
+ npm run build # outputs to src/aleash/static/
28
+ cd ..
29
+ ```
30
+
31
+ ## Running in dev mode
32
+
33
+ Two terminals needed.
34
+
35
+ **Terminal 1 — daemon (foreground so you see logs):**
36
+
37
+ ```sh
38
+ uv run aleash start --foreground
39
+ # → Sandbox UI available on http://localhost:7612/
40
+ ```
41
+
42
+ **Terminal 2 — run an agent:**
43
+
44
+ ```sh
45
+ cd /some/project
46
+ uv run --project /path/to/aleash aleash claude
47
+ ```
48
+
49
+ Open http://localhost:7612/ to see the UI.
50
+
51
+ ## Frontend iteration
52
+
53
+ ```sh
54
+ make frontend
55
+ # → http://localhost:5173/
56
+ ```
57
+
58
+ Vite serves the UI with hot-reload. Edits to `frontend/src/` reflect instantly in the browser.
59
+ Proxies `/api` and `/ws` to the daemon — requires `uv run aleash start --foreground` running first.
60
+
61
+ ## Project layout
62
+
63
+ ```
64
+ src/aleash/
65
+ cli.py # Click commands: start / stop / list / claude / opencode / run
66
+ server.py # FastAPI daemon (REST + WebSocket)
67
+ runner.py # PTY capture, bwrap + mitmproxy orchestration
68
+ proxy_addon.py # mitmproxy addon (domain gating)
69
+ bwrap.py # bubblewrap argv builder
70
+ profiles.py # per-agent bind-mount profiles
71
+ db.py # aiosqlite schema + queries (~/.aleash/data.db)
72
+ notifications.py# notify-send wrapper
73
+
74
+ frontend/src/
75
+ App.vue # root: sidebar + main panel + approval modal
76
+ components/SandboxList.vue # sidebar sandbox list
77
+ components/SandboxDetail.vue # tabs: Terminal | Domains
78
+ components/TerminalPane.vue # xterm.js over WebSocket
79
+ components/DomainsPane.vue # domain decision table
80
+ components/DomainApproval.vue # approval modal
81
+ ```
82
+
83
+ ## Adding an agent profile
84
+
85
+ Edit `src/aleash/profiles.py`:
86
+
87
+ ```python
88
+ _register(Profile(
89
+ name="myagent",
90
+ binary_names=["myagent"], # binary names on PATH to auto-detect
91
+ extra_binds=[
92
+ (_home(".config/myagent"), _home(".config/myagent")),
93
+ ],
94
+ ensure_home_dirs=[".cache/myagent"],
95
+ ))
96
+ ```
97
+
98
+ Then add a Click command in `cli.py` (copy the `claude` command, change the name).
99
+
100
+ ## Building a wheel
101
+
102
+ Requires npm on PATH — the build hook runs `npm ci && npm run build` automatically for wheel builds (skipped for editable installs).
103
+
104
+ ```sh
105
+ uv build
106
+ # outputs dist/aleash-*.whl
107
+ ```
108
+
109
+ ## Data
110
+
111
+ All state lives in `~/.aleash/`:
112
+
113
+ | Path | Content |
114
+ |------|---------|
115
+ | `~/.aleash/data.db` | SQLite: sandboxes, terminal logs, domain decisions |
116
+ | `~/.aleash/server.pid` | daemon PID |
117
+ | `~/.aleash/server.log` | daemon stdout/stderr when daemonized |
118
+ | `~/.mitmproxy/` | mitmproxy CA cert (auto-generated on first run) |
119
+
120
+ Delete `~/.aleash/data.db` to reset all history.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mathieu Lacage
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.
@@ -0,0 +1,11 @@
1
+ .PHONY: all sync frontend
2
+
3
+ all: sync
4
+
5
+ sync:
6
+ uv sync
7
+
8
+ # Dev server: serves UI on :5173 with hot-reload, proxies /api and /ws to the
9
+ # mleash daemon on :7612. Run `uv run mleash start --foreground` first.
10
+ frontend:
11
+ cd frontend && npm run dev
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: agent-leash
3
+ Version: 0.3.0
4
+ Summary: Isolated sandbox for AI coding agents
5
+ Project-URL: Repository, https://github.com/mathieu-lacage/agent-leash.git
6
+ Project-URL: Issues, https://github.com/mathieu-lacage/agent-leash/issues
7
+ Author-email: Mathieu Lacage <mathieu.lacage@cutebugs.net>
8
+ Maintainer-email: Mathieu Lacage <mathieu.lacage@cutebugs.net>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Requires-Python: >=3.11
12
+ Requires-Dist: aiosqlite>=0.20
13
+ Requires-Dist: click>=8.1
14
+ Requires-Dist: fastapi>=0.111
15
+ Requires-Dist: httpx>=0.27
16
+ Requires-Dist: mitmproxy>=10.3
17
+ Requires-Dist: uvicorn[standard]>=0.29
18
+ Provides-Extra: dev
@@ -0,0 +1,85 @@
1
+ # agent-leash
2
+
3
+ Sandbox runner for AI coding agents:
4
+
5
+ - Restricts filesystem access to the current directory
6
+ - Custom allow-list to expose more directories as read-only or read-write
7
+ - Intercepts all network traffic with interactive per-domain approval
8
+ - Controls access to host services (podman, docker, ssh-agent, etc.)
9
+
10
+ ```console
11
+ $ pipx install agent-leash
12
+ $ aleash claude
13
+ Sandbox UI available on http://localhost:7612/
14
+ ```
15
+
16
+ ![aleash demo](docs/demo.gif)
17
+
18
+
19
+ ## How it works
20
+
21
+ - **Filesystem** — [bubblewrap](https://github.com/containers/bubblewrap) restricts the agent to the current working directory. The rest of the filesystem is read-only or hidden.
22
+ - **Network** — [mitmproxy](https://mitmproxy.org/) intercepts all outbound HTTPS. Each new domain triggers a browser popup (and desktop notification). You choose: always allow, allow once, always block, or block once.
23
+ - **Web UI** — Vue 3 + xterm.js frontend served on `localhost:7612`. Shows live terminal output, domain decisions
24
+
25
+ ## Requirements
26
+
27
+ | Tool | Install |
28
+ |------|---------|
29
+ | `bwrap` (bubblewrap) | `dnf install bubblewrap` / `apt install bubblewrap` |
30
+ | `xdg-dbus-proxy` | `dnf install xdg-dbus-proxy` / `apt install xdg-dbus-proxy` |
31
+ | Python ≥ 3.11 | system or [pyenv](https://github.com/pyenv/pyenv) |
32
+
33
+ `mitmproxy` is installed automatically as a Python dependency.
34
+
35
+ ## Usage
36
+
37
+ ### Pass arguments
38
+
39
+ ```sh
40
+ aleash claude -- --dangerously-skip-permissions
41
+ aleash run python script.py --some-flag
42
+ ```
43
+
44
+ ### Terminal size
45
+
46
+ By default the local terminal controls the PTY size. The browser shows the fixed-size terminal with scrollbars. Use `--browser-master` to invert this (browser FitAddon resizes the PTY):
47
+
48
+ ```sh
49
+ aleash --browser-master claude
50
+ ```
51
+
52
+ ### Profile override
53
+
54
+ ```sh
55
+ aleash --profile generic claude # run claude with the generic profile
56
+ ```
57
+
58
+ ## Profiles
59
+
60
+ | Profile | What it binds |
61
+ |---------|--------------|
62
+ | `claude` | `~/.claude`, `~/.claude.json`, `~/.gitconfig`, `~/.local/share/claude` |
63
+ | `opencode` | `~/.opencode`, `~/.gitconfig`, and opencode config/cache dirs |
64
+ | `generic` | nothing extra |
65
+
66
+ `claude` and `opencode` are auto-detected by binary name. Use `--profile` to override.
67
+
68
+ ## Data
69
+
70
+ All state lives in `CWD/.aleash/`:
71
+
72
+ | Path | Content |
73
+ |------|---------|
74
+ | `CWD/.aleash/data.db` | SQLite: sessions, terminal logs, domain decisions |
75
+ | `~/.mitmproxy/` | mitmproxy CA cert (auto-generated on first run) |
76
+
77
+ Delete `CWD/.aleash/data.db` to reset all history.
78
+
79
+ ## Contributing
80
+
81
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
82
+
83
+ ## License
84
+
85
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,37 @@
1
+ # Release process
2
+
3
+ ## How to create a new release
4
+
5
+ The release process is fully automated via GitHub Actions. The workflow is triggered by tagging a commit with a version tag (`v*`).
6
+
7
+ ### Manual steps
8
+
9
+ 1. Bump the version in `pyproject.toml`:
10
+ ```console
11
+ $ uv version --bump minor
12
+ ```
13
+
14
+ 2. Commit the version bump:
15
+ ```console
16
+ $ git add pyproject.toml
17
+ $ git commit -m "release: $(uv version --short)"
18
+ ```
19
+
20
+ 3. Create a version tag (must match the version in `pyproject.toml`):
21
+ ```console
22
+ $ git tag v$(uv version --short)
23
+ ```
24
+
25
+ 4. Push to GitHub (triggers the release workflow):
26
+ ```console
27
+ $ git push github main --tags
28
+ ```
29
+
30
+ ### What happens next (automated)
31
+
32
+ The `.github/workflows/release.yml` workflow automatically:
33
+
34
+ 1. **Validates** the tag matches the version in `pyproject.toml`
35
+ 5. **Builds** the Python distribution (wheel + sdist)
36
+ 6. **Publishes to PyPI** (via OIDC Trusted Publisher, no token needed)
37
+ 8. **Creates a GitHub Release** with both the Python dist and the squashfs artifact
Binary file
@@ -0,0 +1,36 @@
1
+ import js from '@eslint/js'
2
+ import tseslint from 'typescript-eslint'
3
+ import pluginVue from 'eslint-plugin-vue'
4
+ import globals from 'globals'
5
+
6
+ export default tseslint.config(
7
+ js.configs.recommended,
8
+ ...tseslint.configs.recommended,
9
+ ...pluginVue.configs['flat/recommended'],
10
+ {
11
+ files: ['**/*.vue', '**/*.ts'],
12
+ languageOptions: {
13
+ globals: {
14
+ ...globals.browser,
15
+ },
16
+ parserOptions: {
17
+ parser: tseslint.parser,
18
+ project: './tsconfig.json',
19
+ extraFileExtensions: ['.vue'],
20
+ sourceType: 'module',
21
+ },
22
+ },
23
+ rules: {
24
+ 'vue/multi-word-component-names': 'off',
25
+ '@typescript-eslint/no-explicit-any': 'warn',
26
+ // Template formatting rules conflict with existing compact style
27
+ 'vue/max-attributes-per-line': 'off',
28
+ 'vue/singleline-html-element-content-newline': 'off',
29
+ 'vue/multiline-html-element-content-newline': 'off',
30
+ 'vue/attributes-order': 'off',
31
+ },
32
+ },
33
+ {
34
+ ignores: ['dist/**', 'node_modules/**'],
35
+ },
36
+ )
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Sandbox</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.ts"></script>
11
+ </body>
12
+ </html>