aaw-core 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.
- aaw_core-0.1.0/.dockerignore +10 -0
- aaw_core-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +10 -0
- aaw_core-0.1.0/.github/workflows/ci.yml +46 -0
- aaw_core-0.1.0/.gitignore +33 -0
- aaw_core-0.1.0/CODE_OF_CONDUCT.md +9 -0
- aaw_core-0.1.0/CONTRIBUTING.md +44 -0
- aaw_core-0.1.0/LICENSE +21 -0
- aaw_core-0.1.0/PKG-INFO +138 -0
- aaw_core-0.1.0/README.md +101 -0
- aaw_core-0.1.0/SECURITY.md +18 -0
- aaw_core-0.1.0/aaw_core/__init__.py +7 -0
- aaw_core-0.1.0/aaw_core/agents/__init__.py +0 -0
- aaw_core-0.1.0/aaw_core/config.py +151 -0
- aaw_core-0.1.0/aaw_core/daemon.py +712 -0
- aaw_core-0.1.0/aaw_core/encryption.py +96 -0
- aaw_core-0.1.0/aaw_core/hooks/__init__.py +0 -0
- aaw_core-0.1.0/aaw_core/hooks/common.py +201 -0
- aaw_core-0.1.0/aaw_core/hooks/on_notification.py +39 -0
- aaw_core-0.1.0/aaw_core/hooks/on_post_tool.py +57 -0
- aaw_core-0.1.0/aaw_core/hooks/on_pre_tool.py +195 -0
- aaw_core-0.1.0/aaw_core/hooks/on_session_start.py +65 -0
- aaw_core-0.1.0/aaw_core/hooks/on_stop.py +114 -0
- aaw_core-0.1.0/aaw_core/hooks/on_stop_claude.py +287 -0
- aaw_core-0.1.0/aaw_core/hooks/on_stop_cursor.py +51 -0
- aaw_core-0.1.0/aaw_core/hooks/on_stop_gemini.py +14 -0
- aaw_core-0.1.0/aaw_core/hooks/on_stop_grok.py +206 -0
- aaw_core-0.1.0/aaw_core/hooks/on_user_prompt.py +43 -0
- aaw_core-0.1.0/aaw_core/host/__init__.py +0 -0
- aaw_core-0.1.0/aaw_core/host/cli.py +846 -0
- aaw_core-0.1.0/aaw_core/host/detect.py +449 -0
- aaw_core-0.1.0/aaw_core/host/fs.py +162 -0
- aaw_core-0.1.0/aaw_core/host/hooks_installer.py +376 -0
- aaw_core-0.1.0/aaw_core/host/identity.py +54 -0
- aaw_core-0.1.0/aaw_core/host/scripts/shell_integration.sh +58 -0
- aaw_core-0.1.0/aaw_core/host/service.py +209 -0
- aaw_core-0.1.0/aaw_core/host/session_id.py +75 -0
- aaw_core-0.1.0/aaw_core/host/sessions.py +388 -0
- aaw_core-0.1.0/aaw_core/host/state.py +97 -0
- aaw_core-0.1.0/aaw_core/host/supervisor.py +405 -0
- aaw_core-0.1.0/aaw_core/host/tmux.py +201 -0
- aaw_core-0.1.0/aaw_core/host/usage.py +283 -0
- aaw_core-0.1.0/aaw_core/relay/__init__.py +8 -0
- aaw_core-0.1.0/aaw_core/relay/__main__.py +44 -0
- aaw_core-0.1.0/aaw_core/relay/backup.py +56 -0
- aaw_core-0.1.0/aaw_core/relay/push_fcm.py +164 -0
- aaw_core-0.1.0/aaw_core/relay/server.py +646 -0
- aaw_core-0.1.0/aaw_core/transport/__init__.py +0 -0
- aaw_core-0.1.0/aaw_core/transport/base.py +225 -0
- aaw_core-0.1.0/aaw_core/transport/relay.py +470 -0
- aaw_core-0.1.0/aaw_core/transport/utils.py +51 -0
- aaw_core-0.1.0/docker/README.md +40 -0
- aaw_core-0.1.0/docker/linux-host.Dockerfile +20 -0
- aaw_core-0.1.0/docs/.gitkeep +0 -0
- aaw_core-0.1.0/docs/architecture.md +85 -0
- aaw_core-0.1.0/docs/protocol.contract.md +127 -0
- aaw_core-0.1.0/pyproject.toml +58 -0
- aaw_core-0.1.0/relay/Dockerfile +16 -0
- aaw_core-0.1.0/relay/README.md +55 -0
- aaw_core-0.1.0/relay/docker-compose.yml +22 -0
- aaw_core-0.1.0/scripts/install.sh +143 -0
- aaw_core-0.1.0/tests/test_cli.py +184 -0
- aaw_core-0.1.0/tests/test_config.py +60 -0
- aaw_core-0.1.0/tests/test_detect.py +240 -0
- aaw_core-0.1.0/tests/test_e2e_host.py +116 -0
- aaw_core-0.1.0/tests/test_encryption.py +98 -0
- aaw_core-0.1.0/tests/test_fs.py +67 -0
- aaw_core-0.1.0/tests/test_hooks.py +319 -0
- aaw_core-0.1.0/tests/test_hooks_installer.py +144 -0
- aaw_core-0.1.0/tests/test_push_fcm.py +153 -0
- aaw_core-0.1.0/tests/test_relay.py +618 -0
- aaw_core-0.1.0/tests/test_relay_backup.py +25 -0
- aaw_core-0.1.0/tests/test_service.py +65 -0
- aaw_core-0.1.0/tests/test_session_id.py +55 -0
- aaw_core-0.1.0/tests/test_sessions.py +99 -0
- aaw_core-0.1.0/tests/test_smoke.py +37 -0
- aaw_core-0.1.0/tests/test_state.py +60 -0
- aaw_core-0.1.0/tests/test_supervisor.py +201 -0
- aaw_core-0.1.0/tests/test_transport_base.py +114 -0
- aaw_core-0.1.0/tests/test_utils.py +36 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
## What this changes
|
|
2
|
+
|
|
3
|
+
<!-- One or two sentences. Link the issue or discussion if there is one. -->
|
|
4
|
+
|
|
5
|
+
## Checklist
|
|
6
|
+
|
|
7
|
+
- [ ] Every commit is signed off (`git commit -s`), per the DCO in CONTRIBUTING.md.
|
|
8
|
+
- [ ] No secrets, tokens, or hosted-project defaults were added; config comes from env or `~/.aaw/config.json`.
|
|
9
|
+
- [ ] Tests pass locally (`pytest`) and lint is clean (`ruff check .`).
|
|
10
|
+
- [ ] If this touches encryption, pairing, `aaw_core/transport/`, or `relay/`: the event/command shapes and the encryption envelope stay compatible with the phone apps (see `docs/protocol.contract.md`).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Build, lint, test, and scan for secrets. Never deploys anything.
|
|
2
|
+
# Fork pull requests get no repository secrets.
|
|
3
|
+
name: ci
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
pull_request:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
os: [ubuntu-latest, macos-latest]
|
|
20
|
+
python: ["3.11", "3.12", "3.13"]
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: ${{ matrix.python }}
|
|
26
|
+
- name: tmux (the end-to-end host test needs it)
|
|
27
|
+
run: |
|
|
28
|
+
if [ "$RUNNER_OS" = "Linux" ]; then sudo apt-get update -q && sudo apt-get install -y -q tmux; fi
|
|
29
|
+
if [ "$RUNNER_OS" = "macOS" ]; then brew list tmux >/dev/null 2>&1 || brew install -q tmux; fi
|
|
30
|
+
tmux -V
|
|
31
|
+
- run: python -m pip install --upgrade pip
|
|
32
|
+
- run: pip install -e ".[dev,relay]"
|
|
33
|
+
- run: ruff check .
|
|
34
|
+
- run: pytest -q
|
|
35
|
+
- run: aaw --version
|
|
36
|
+
|
|
37
|
+
secrets:
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
with:
|
|
42
|
+
fetch-depth: 0
|
|
43
|
+
- name: gitleaks
|
|
44
|
+
uses: gitleaks/gitleaks-action@v2
|
|
45
|
+
env:
|
|
46
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
.pytest_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
|
|
13
|
+
# Local config, state, logs
|
|
14
|
+
.env
|
|
15
|
+
.env.*
|
|
16
|
+
*.log
|
|
17
|
+
*.jsonl
|
|
18
|
+
.aaw/
|
|
19
|
+
locks/
|
|
20
|
+
*.pid
|
|
21
|
+
|
|
22
|
+
# OS / editors
|
|
23
|
+
.DS_Store
|
|
24
|
+
.idea/
|
|
25
|
+
.vscode/
|
|
26
|
+
|
|
27
|
+
# Belt and braces: secrets never belong here
|
|
28
|
+
*.p8
|
|
29
|
+
*.p12
|
|
30
|
+
*.cer
|
|
31
|
+
*.provisionprofile
|
|
32
|
+
*service-account*.json
|
|
33
|
+
*secrets*.env
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
This project follows the [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
|
|
4
|
+
|
|
5
|
+
In short: be respectful and constructive, assume good faith, and keep discussions about the work. Harassment, personal attacks, and discriminatory language are not tolerated anywhere this project happens: issues, pull requests, discussions, and any linked channels.
|
|
6
|
+
|
|
7
|
+
## Enforcement
|
|
8
|
+
|
|
9
|
+
Report unacceptable behavior to **hello@agentsatwork.app**. Reports are handled privately and confidentially. Maintainers may edit, remove, or reject contributions and comments that violate this code, and may temporarily or permanently ban contributors for repeated or severe violations.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Contributing to Agents At Work Core
|
|
2
|
+
|
|
3
|
+
Thanks for helping. This is the open-source engine behind Agents At Work; the apps are closed, but everything that runs on your computer lives here.
|
|
4
|
+
|
|
5
|
+
## Developer Certificate of Origin (DCO)
|
|
6
|
+
|
|
7
|
+
Every commit must be signed off:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git commit -s -m "your message"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
That adds a `Signed-off-by: Your Name <you@example.com>` trailer, certifying you have the right to contribute the change under the MIT license (the [DCO 1.1](https://developercertificate.org/)). A DCO check runs on every pull request; unsigned commits fail it. There is no CLA.
|
|
14
|
+
|
|
15
|
+
## Development setup
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
git clone https://github.com/sergenes/aaw-core
|
|
19
|
+
cd aaw-core
|
|
20
|
+
python -m venv .venv && source .venv/bin/activate
|
|
21
|
+
pip install -e ".[dev,relay]"
|
|
22
|
+
pytest
|
|
23
|
+
ruff check .
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Python 3.11+. The host runs on Linux and macOS; the relay runs anywhere Python does.
|
|
27
|
+
|
|
28
|
+
## What gets extra scrutiny
|
|
29
|
+
|
|
30
|
+
Changes to these paths are reviewed more slowly and carefully, because they affect security or compatibility with the closed phone apps:
|
|
31
|
+
|
|
32
|
+
- `aaw_core/encryption.py`: the AES-256-GCM envelope must stay byte-compatible with the apps.
|
|
33
|
+
- Pairing and the QR payload.
|
|
34
|
+
- `aaw_core/transport/` and `relay/`: event and command shapes are a contract with the apps (see `docs/protocol.contract.md`).
|
|
35
|
+
|
|
36
|
+
## Before opening a pull request
|
|
37
|
+
|
|
38
|
+
- For anything non-trivial, open an issue or discussion first so you do not spend a weekend on something that conflicts with the roadmap.
|
|
39
|
+
- CI must be green: build, tests, lint, and the secret scanner.
|
|
40
|
+
- Never commit secrets, keys, tokens, or anything pointing at a specific hosted project. Configuration comes from environment variables and `~/.aaw/config.json`; the code has no hosted defaults.
|
|
41
|
+
|
|
42
|
+
## How this project is developed
|
|
43
|
+
|
|
44
|
+
The engine is developed here, in the open. It was assembled by porting the host from the maintainer's product, Agents At Work, and it is now the engine that product runs: the Mac app bundles it, and the hosted relay is this relay. Good contributions here reach both.
|
aaw_core-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sergey Nes
|
|
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.
|
aaw_core-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: aaw-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agents At Work Core: the open-source engine behind Agents At Work. Run Claude Code, Codex, Gemini CLI, Grok, Cursor, and Scoot on your own Mac or Linux box and control them from your phone.
|
|
5
|
+
Project-URL: Homepage, https://agentsatwork.app
|
|
6
|
+
Project-URL: Repository, https://github.com/sergenes/aaw-core
|
|
7
|
+
Project-URL: Issues, https://github.com/sergenes/aaw-core/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/sergenes/aaw-core/releases
|
|
9
|
+
Author: Sergey Nes
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: ai-agents,claude-code,codex,gemini-cli,remote-control,tmux
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Software Development
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: cryptography>=42
|
|
26
|
+
Requires-Dist: qrcode>=7
|
|
27
|
+
Requires-Dist: websockets>=12
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: httpx2; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
32
|
+
Provides-Extra: relay
|
|
33
|
+
Requires-Dist: aiosqlite>=0.20; extra == 'relay'
|
|
34
|
+
Requires-Dist: starlette>=0.37; extra == 'relay'
|
|
35
|
+
Requires-Dist: uvicorn>=0.29; extra == 'relay'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# Agents At Work Core
|
|
39
|
+
|
|
40
|
+
[](https://pypi.org/project/aaw-core/)
|
|
41
|
+
[](https://github.com/sergenes/aaw-core/actions/workflows/ci.yml)
|
|
42
|
+
[](https://www.python.org/downloads/)
|
|
43
|
+
[](#install)
|
|
44
|
+
[](#supported-agents)
|
|
45
|
+
[](./LICENSE)
|
|
46
|
+
|
|
47
|
+
**The open-source engine behind [Agents At Work](https://agentsatwork.app).**
|
|
48
|
+
Run Claude Code, Codex, Gemini CLI, Grok, Cursor, and scoot on your own Mac or Linux box, and follow them from your phone: approve tool calls, answer questions, send prompts, and schedule prompts for later, end-to-end encrypted.
|
|
49
|
+
|
|
50
|
+
> **Status: alpha.**
|
|
51
|
+
> This is the engine the Agents At Work Mac app bundles and the hosted relay runs, so it is in daily use, but the command line and the config keys can still change between 0.x releases.
|
|
52
|
+
|
|
53
|
+
## What is open and what is not
|
|
54
|
+
|
|
55
|
+
`aaw-core` is everything that runs on *your* computer: the daemon that watches your agents in `tmux`, the hooks that report what they do, and the `aaw` command line.
|
|
56
|
+
It also contains the relay server that connects your computer to your phone.
|
|
57
|
+
All of it is MIT licensed.
|
|
58
|
+
|
|
59
|
+
The phone apps (iPhone, iPad, Android) and the Mac menu-bar app are not in this repository.
|
|
60
|
+
The Mac app is a free download that runs this same engine.
|
|
61
|
+
The phone apps are free for one computer on our hosted relay; the Personal and Pro plans raise that to 3 and 15 computers.
|
|
62
|
+
Computers on a relay you host yourself never count toward a plan.
|
|
63
|
+
|
|
64
|
+
## How it works
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
your computer your phone
|
|
68
|
+
agent in tmux <-> aaw daemon <-> local store store app
|
|
69
|
+
| ^
|
|
70
|
+
| one outbound WebSocket |
|
|
71
|
+
v |
|
|
72
|
+
the relay (tiny; ciphertext + routing only) -> push (FCM/APNs)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- Your **local store** on the computer is the source of truth for the conversation history.
|
|
76
|
+
- The daemon keeps **one outbound WebSocket** to a relay, so it works behind any home router or cellular NAT with **no ports, no tunnel, no VPN, no Firebase project**.
|
|
77
|
+
- The relay buffers what is in flight while the phone is asleep and triggers a wake-up push. It only ever sees **ciphertext and routing metadata**: prompts, answers, and content are AES-256-GCM encrypted with a key that only your computer and your phone hold, exchanged once by QR.
|
|
78
|
+
- We host a relay for free users; self-hosters run the same server with `docker compose`.
|
|
79
|
+
|
|
80
|
+
## Install
|
|
81
|
+
|
|
82
|
+
On Linux, one command installs or updates everything: its own virtualenv, the `aaw` command, the shell integration, the link with your phone (a QR code), and the always-on service.
|
|
83
|
+
It also removes the earlier Agents At Work Linux host if it finds one.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
curl -fsSL https://agentsatwork.app/install-linux.sh | bash # the same script as scripts/install.sh
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Or by hand, on Linux or macOS (on a Mac, the free Agents At Work app does all of this for you):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pipx install aaw-core # Python 3.11+, and tmux
|
|
93
|
+
aaw link # asks which relay (ours, or your own), prints a QR code to scan
|
|
94
|
+
aaw service install # the always-on part: runs in the background and starts at every login
|
|
95
|
+
aaw shell-integration on # typing claude, codex, ... in a folder starts a bridged session
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Then start an agent the way you always do (`cd ~/my-project && claude`), or start one from the phone.
|
|
99
|
+
|
|
100
|
+
## Everyday commands
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
aaw status # the computer, the relay, and every session
|
|
104
|
+
aaw start ~/my-project # start a session (--agent codex, gemini, grok, cursor, scoot)
|
|
105
|
+
aaw stop my-project # stop one session
|
|
106
|
+
aaw feed my-project # the conversation, paged; -f follows it live
|
|
107
|
+
aaw send my-project "run the tests" # send a prompt
|
|
108
|
+
aaw schedule my-project --at 2am "continue with the refactor" # a prompt for later
|
|
109
|
+
aaw mobile-mode on # permission prompts go to the phone
|
|
110
|
+
aaw quit # stop everything; the next login (or aaw service start) brings it back
|
|
111
|
+
aaw uninstall # remove the service, the hooks, the shell line, and ~/.aaw
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`aaw --help` lists every command, and `aaw <command> --help` its options.
|
|
115
|
+
|
|
116
|
+
## Hosting the relay yourself
|
|
117
|
+
|
|
118
|
+
The relay is a small server in this repository (`aaw_core/relay/`): run it with `docker compose` from `relay/`, or with `pip install "aaw-core[relay]"` and `python -m aaw_core.relay`, behind a TLS proxy.
|
|
119
|
+
Point your computer at it with `aaw link --relay wss://your.host/v1/ws`.
|
|
120
|
+
It sees only ciphertext and routing metadata, the same as ours.
|
|
121
|
+
One difference: waking the phone with a push notification needs the app's own push credentials, so on your own relay the phone gets updates while the app is open, not in the background.
|
|
122
|
+
`relay/README.md` has the details.
|
|
123
|
+
|
|
124
|
+
## Desktop notifications
|
|
125
|
+
|
|
126
|
+
The phone is the primary channel, and the computer itself also gets a banner when a turn finishes, an error happens, or the agent waits for input: `osascript` on macOS, `notify-send` on a Linux desktop (GNOME, KDE, and the rest). A server or a box without a desktop simply has no `notify-send` and shows nothing. On macOS, while any Focus is on (Do Not Disturb included), banners from scripts go to Notification Center silently unless Agents At Work is allowed under System Settings > Focus > Allowed Apps; `aaw status` reminds you. Two keys in `~/.aaw/config.json`: `local_notifications` (default `true`; a GUI that shows its own banners turns it off) and `waiting_alert_seconds` (default `0`, off; the seconds a question may sit unanswered before a "waiting for your answer" banner, for when the push is muted while you are at the desk).
|
|
127
|
+
|
|
128
|
+
## Supported agents
|
|
129
|
+
|
|
130
|
+
Claude Code, Codex CLI, Gemini CLI, Grok CLI, Cursor, and [Scoot](https://github.com/sergenes/scootcli) (local models with Ollama, plus cloud models). Not affiliated with Anthropic, OpenAI, Google, xAI, Anysphere, or Ollama.
|
|
131
|
+
|
|
132
|
+
## Contributing
|
|
133
|
+
|
|
134
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Contributions use the DCO (`git commit -s`). Security reports: see [SECURITY.md](SECURITY.md).
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT. See [LICENSE](LICENSE).
|
aaw_core-0.1.0/README.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Agents At Work Core
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/aaw-core/)
|
|
4
|
+
[](https://github.com/sergenes/aaw-core/actions/workflows/ci.yml)
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
[](#install)
|
|
7
|
+
[](#supported-agents)
|
|
8
|
+
[](./LICENSE)
|
|
9
|
+
|
|
10
|
+
**The open-source engine behind [Agents At Work](https://agentsatwork.app).**
|
|
11
|
+
Run Claude Code, Codex, Gemini CLI, Grok, Cursor, and scoot on your own Mac or Linux box, and follow them from your phone: approve tool calls, answer questions, send prompts, and schedule prompts for later, end-to-end encrypted.
|
|
12
|
+
|
|
13
|
+
> **Status: alpha.**
|
|
14
|
+
> This is the engine the Agents At Work Mac app bundles and the hosted relay runs, so it is in daily use, but the command line and the config keys can still change between 0.x releases.
|
|
15
|
+
|
|
16
|
+
## What is open and what is not
|
|
17
|
+
|
|
18
|
+
`aaw-core` is everything that runs on *your* computer: the daemon that watches your agents in `tmux`, the hooks that report what they do, and the `aaw` command line.
|
|
19
|
+
It also contains the relay server that connects your computer to your phone.
|
|
20
|
+
All of it is MIT licensed.
|
|
21
|
+
|
|
22
|
+
The phone apps (iPhone, iPad, Android) and the Mac menu-bar app are not in this repository.
|
|
23
|
+
The Mac app is a free download that runs this same engine.
|
|
24
|
+
The phone apps are free for one computer on our hosted relay; the Personal and Pro plans raise that to 3 and 15 computers.
|
|
25
|
+
Computers on a relay you host yourself never count toward a plan.
|
|
26
|
+
|
|
27
|
+
## How it works
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
your computer your phone
|
|
31
|
+
agent in tmux <-> aaw daemon <-> local store store app
|
|
32
|
+
| ^
|
|
33
|
+
| one outbound WebSocket |
|
|
34
|
+
v |
|
|
35
|
+
the relay (tiny; ciphertext + routing only) -> push (FCM/APNs)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- Your **local store** on the computer is the source of truth for the conversation history.
|
|
39
|
+
- The daemon keeps **one outbound WebSocket** to a relay, so it works behind any home router or cellular NAT with **no ports, no tunnel, no VPN, no Firebase project**.
|
|
40
|
+
- The relay buffers what is in flight while the phone is asleep and triggers a wake-up push. It only ever sees **ciphertext and routing metadata**: prompts, answers, and content are AES-256-GCM encrypted with a key that only your computer and your phone hold, exchanged once by QR.
|
|
41
|
+
- We host a relay for free users; self-hosters run the same server with `docker compose`.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
On Linux, one command installs or updates everything: its own virtualenv, the `aaw` command, the shell integration, the link with your phone (a QR code), and the always-on service.
|
|
46
|
+
It also removes the earlier Agents At Work Linux host if it finds one.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
curl -fsSL https://agentsatwork.app/install-linux.sh | bash # the same script as scripts/install.sh
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or by hand, on Linux or macOS (on a Mac, the free Agents At Work app does all of this for you):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pipx install aaw-core # Python 3.11+, and tmux
|
|
56
|
+
aaw link # asks which relay (ours, or your own), prints a QR code to scan
|
|
57
|
+
aaw service install # the always-on part: runs in the background and starts at every login
|
|
58
|
+
aaw shell-integration on # typing claude, codex, ... in a folder starts a bridged session
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then start an agent the way you always do (`cd ~/my-project && claude`), or start one from the phone.
|
|
62
|
+
|
|
63
|
+
## Everyday commands
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
aaw status # the computer, the relay, and every session
|
|
67
|
+
aaw start ~/my-project # start a session (--agent codex, gemini, grok, cursor, scoot)
|
|
68
|
+
aaw stop my-project # stop one session
|
|
69
|
+
aaw feed my-project # the conversation, paged; -f follows it live
|
|
70
|
+
aaw send my-project "run the tests" # send a prompt
|
|
71
|
+
aaw schedule my-project --at 2am "continue with the refactor" # a prompt for later
|
|
72
|
+
aaw mobile-mode on # permission prompts go to the phone
|
|
73
|
+
aaw quit # stop everything; the next login (or aaw service start) brings it back
|
|
74
|
+
aaw uninstall # remove the service, the hooks, the shell line, and ~/.aaw
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`aaw --help` lists every command, and `aaw <command> --help` its options.
|
|
78
|
+
|
|
79
|
+
## Hosting the relay yourself
|
|
80
|
+
|
|
81
|
+
The relay is a small server in this repository (`aaw_core/relay/`): run it with `docker compose` from `relay/`, or with `pip install "aaw-core[relay]"` and `python -m aaw_core.relay`, behind a TLS proxy.
|
|
82
|
+
Point your computer at it with `aaw link --relay wss://your.host/v1/ws`.
|
|
83
|
+
It sees only ciphertext and routing metadata, the same as ours.
|
|
84
|
+
One difference: waking the phone with a push notification needs the app's own push credentials, so on your own relay the phone gets updates while the app is open, not in the background.
|
|
85
|
+
`relay/README.md` has the details.
|
|
86
|
+
|
|
87
|
+
## Desktop notifications
|
|
88
|
+
|
|
89
|
+
The phone is the primary channel, and the computer itself also gets a banner when a turn finishes, an error happens, or the agent waits for input: `osascript` on macOS, `notify-send` on a Linux desktop (GNOME, KDE, and the rest). A server or a box without a desktop simply has no `notify-send` and shows nothing. On macOS, while any Focus is on (Do Not Disturb included), banners from scripts go to Notification Center silently unless Agents At Work is allowed under System Settings > Focus > Allowed Apps; `aaw status` reminds you. Two keys in `~/.aaw/config.json`: `local_notifications` (default `true`; a GUI that shows its own banners turns it off) and `waiting_alert_seconds` (default `0`, off; the seconds a question may sit unanswered before a "waiting for your answer" banner, for when the push is muted while you are at the desk).
|
|
90
|
+
|
|
91
|
+
## Supported agents
|
|
92
|
+
|
|
93
|
+
Claude Code, Codex CLI, Gemini CLI, Grok CLI, Cursor, and [Scoot](https://github.com/sergenes/scootcli) (local models with Ollama, plus cloud models). Not affiliated with Anthropic, OpenAI, Google, xAI, Anysphere, or Ollama.
|
|
94
|
+
|
|
95
|
+
## Contributing
|
|
96
|
+
|
|
97
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Contributions use the DCO (`git commit -s`). Security reports: see [SECURITY.md](SECURITY.md).
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
This project handles end-to-end encryption and remote control of coding agents. Please report vulnerabilities privately, not in the public issue tracker.
|
|
4
|
+
|
|
5
|
+
## Reporting
|
|
6
|
+
|
|
7
|
+
- Preferred: open a private report through **GitHub Security Advisories** on this repository ("Report a vulnerability").
|
|
8
|
+
- Or email **hello@agentsatwork.app** with the details.
|
|
9
|
+
|
|
10
|
+
Include what you found, how to reproduce it, and the impact you believe it has. You will get an acknowledgement, and a fix or a mitigation plan, before any public disclosure.
|
|
11
|
+
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
- The host (`aaw_core/`): the daemon, hooks, pairing, and the `aaw` command.
|
|
15
|
+
- The relay (`relay/`).
|
|
16
|
+
- The encryption envelope and key handling.
|
|
17
|
+
|
|
18
|
+
The closed phone and macOS apps are out of scope here; report those to the same email.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Agents At Work Core: the open-source engine behind Agents At Work.
|
|
2
|
+
|
|
3
|
+
The headless host (daemon, hooks, ``aaw`` CLI) that watches coding agents in tmux
|
|
4
|
+
on your own computer, plus the relay transport that connects it to your phone.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
File without changes
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Configuration for the aaw-core host.
|
|
2
|
+
|
|
3
|
+
Config-first by construction: everything comes from environment variables or
|
|
4
|
+
``~/.aaw/config.json``. There are no defaults that point at any hosted project,
|
|
5
|
+
which is what makes this repository safe to publish without a secrets audit.
|
|
6
|
+
|
|
7
|
+
Precedence: environment variable > config file > built-in default.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
# A distinct state directory so aaw-core can coexist on one machine with the
|
|
20
|
+
# commercial Agents At Work host, which uses ~/.agent-bridge.
|
|
21
|
+
DEFAULT_STATE_DIR = Path.home() / ".aaw"
|
|
22
|
+
|
|
23
|
+
# The relay we host for free users. A public endpoint, not a secret, and never a
|
|
24
|
+
# silent default: `aaw link` offers it and writes the choice to config.json.
|
|
25
|
+
HOSTED_RELAY_URL = "wss://relay.agentsatwork.app/v1/ws"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def default_computer_name() -> str:
|
|
29
|
+
"""The name a person knows this computer by.
|
|
30
|
+
|
|
31
|
+
On macOS that is the Computer Name from System Settings ("Sergey's MacBook Pro"),
|
|
32
|
+
not the network hostname ("Sergeys-MacBook-Pro.local"). Elsewhere the hostname,
|
|
33
|
+
without a trailing ".local".
|
|
34
|
+
"""
|
|
35
|
+
if sys.platform == "darwin":
|
|
36
|
+
try:
|
|
37
|
+
out = subprocess.run(["scutil", "--get", "ComputerName"], capture_output=True, text=True,
|
|
38
|
+
timeout=2, check=False).stdout.strip()
|
|
39
|
+
if out:
|
|
40
|
+
return out
|
|
41
|
+
except (OSError, subprocess.SubprocessError):
|
|
42
|
+
pass
|
|
43
|
+
name = os.uname().nodename
|
|
44
|
+
return name.removesuffix(".local")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _config_file(state_dir: Path) -> dict:
|
|
48
|
+
path = state_dir / "config.json"
|
|
49
|
+
try:
|
|
50
|
+
return json.loads(path.read_text()) if path.exists() else {}
|
|
51
|
+
except (OSError, ValueError):
|
|
52
|
+
return {}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass(frozen=True)
|
|
56
|
+
class Settings:
|
|
57
|
+
"""Resolved settings for one host process."""
|
|
58
|
+
|
|
59
|
+
state_dir: Path
|
|
60
|
+
relay_url: str | None # wss://... ; required to bridge to a phone, None = local only
|
|
61
|
+
computer_name: str
|
|
62
|
+
keep_awake: bool
|
|
63
|
+
browse_roots: tuple[str, ...] = ("~",) # folders the phone may browse and start sessions in
|
|
64
|
+
local_notifications: bool = True # desktop banners (osascript / notify-send)
|
|
65
|
+
waiting_alert_seconds: int = 0 # 0 = no "waiting for your answer" desktop alert
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def sessions_dir(self) -> Path:
|
|
69
|
+
return self.state_dir / "sessions"
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def run_dir(self) -> Path:
|
|
73
|
+
"""Pid files of the daemons and the keep-awake helper."""
|
|
74
|
+
return self.state_dir / "run"
|
|
75
|
+
|
|
76
|
+
def daemon_pid_file(self, project_id: str) -> Path:
|
|
77
|
+
return self.run_dir / f"daemon.{project_id}.pid"
|
|
78
|
+
|
|
79
|
+
def daemon_log_file(self, project_id: str) -> Path:
|
|
80
|
+
return self.logs_dir / f"daemon.{project_id}.log"
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def keep_awake_pid_file(self) -> Path:
|
|
84
|
+
return self.run_dir / "keep-awake.pid"
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def supervisor_log_file(self) -> Path:
|
|
88
|
+
return self.logs_dir / "supervisor.log"
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def session_key_file(self) -> Path:
|
|
92
|
+
return self.state_dir / "session.key"
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def enabled_flag(self) -> Path:
|
|
96
|
+
return self.state_dir / "enabled"
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def mobile_mode_file(self) -> Path:
|
|
100
|
+
"""Holds "manual" or a timestamp while permission prompts should go to the phone."""
|
|
101
|
+
return self.state_dir / "mobile_mode"
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def host_file(self) -> Path:
|
|
105
|
+
"""The persisted host identity (computer id + relay routing token)."""
|
|
106
|
+
return self.state_dir / "host.json"
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def logs_dir(self) -> Path:
|
|
110
|
+
return self.state_dir / "logs"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def save_config(state_dir: Path, **fields) -> Path:
|
|
114
|
+
"""Merge fields into <state dir>/config.json (created if missing)."""
|
|
115
|
+
path = state_dir / "config.json"
|
|
116
|
+
current = _config_file(state_dir)
|
|
117
|
+
current.update(fields)
|
|
118
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
119
|
+
path.write_text(json.dumps(current, indent=2) + "\n")
|
|
120
|
+
return path
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def load_settings() -> Settings:
|
|
124
|
+
state_dir = Path(os.environ.get("AAW_STATE_DIR") or DEFAULT_STATE_DIR).expanduser()
|
|
125
|
+
cfg = _config_file(state_dir)
|
|
126
|
+
|
|
127
|
+
def pick(env: str, key: str, default):
|
|
128
|
+
value = os.environ.get(env)
|
|
129
|
+
if value is not None and value != "":
|
|
130
|
+
return value
|
|
131
|
+
return cfg.get(key, default)
|
|
132
|
+
|
|
133
|
+
def flag(env: str, key: str, default: str) -> bool:
|
|
134
|
+
return str(pick(env, key, default)).lower() in ("1", "true", "yes")
|
|
135
|
+
|
|
136
|
+
roots = pick("AAW_BROWSE_ROOTS", "browse_roots", "~")
|
|
137
|
+
if isinstance(roots, str):
|
|
138
|
+
roots = [r for r in roots.split(os.pathsep) if r]
|
|
139
|
+
try:
|
|
140
|
+
waiting = int(pick("AAW_WAITING_ALERT_SECONDS", "waiting_alert_seconds", 0))
|
|
141
|
+
except (TypeError, ValueError):
|
|
142
|
+
waiting = 0
|
|
143
|
+
return Settings(
|
|
144
|
+
state_dir=state_dir,
|
|
145
|
+
relay_url=pick("AAW_RELAY_URL", "relay_url", None),
|
|
146
|
+
computer_name=pick("AAW_COMPUTER_NAME", "computer_name", None) or default_computer_name(),
|
|
147
|
+
keep_awake=flag("AAW_KEEP_AWAKE", "keep_awake", "true"),
|
|
148
|
+
browse_roots=tuple(str(r) for r in roots) or ("~",),
|
|
149
|
+
local_notifications=flag("AAW_LOCAL_NOTIFICATIONS", "local_notifications", "true"),
|
|
150
|
+
waiting_alert_seconds=max(0, waiting),
|
|
151
|
+
)
|