aaw-core 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.
- aaw_core/__init__.py +7 -0
- aaw_core/agents/__init__.py +0 -0
- aaw_core/config.py +151 -0
- aaw_core/daemon.py +712 -0
- aaw_core/encryption.py +96 -0
- aaw_core/hooks/__init__.py +0 -0
- aaw_core/hooks/common.py +201 -0
- aaw_core/hooks/on_notification.py +39 -0
- aaw_core/hooks/on_post_tool.py +57 -0
- aaw_core/hooks/on_pre_tool.py +195 -0
- aaw_core/hooks/on_session_start.py +65 -0
- aaw_core/hooks/on_stop.py +114 -0
- aaw_core/hooks/on_stop_claude.py +287 -0
- aaw_core/hooks/on_stop_cursor.py +51 -0
- aaw_core/hooks/on_stop_gemini.py +14 -0
- aaw_core/hooks/on_stop_grok.py +206 -0
- aaw_core/hooks/on_user_prompt.py +43 -0
- aaw_core/host/__init__.py +0 -0
- aaw_core/host/cli.py +846 -0
- aaw_core/host/detect.py +449 -0
- aaw_core/host/fs.py +162 -0
- aaw_core/host/hooks_installer.py +376 -0
- aaw_core/host/identity.py +54 -0
- aaw_core/host/scripts/shell_integration.sh +58 -0
- aaw_core/host/service.py +209 -0
- aaw_core/host/session_id.py +75 -0
- aaw_core/host/sessions.py +388 -0
- aaw_core/host/state.py +97 -0
- aaw_core/host/supervisor.py +405 -0
- aaw_core/host/tmux.py +201 -0
- aaw_core/host/usage.py +283 -0
- aaw_core/relay/__init__.py +8 -0
- aaw_core/relay/__main__.py +44 -0
- aaw_core/relay/backup.py +56 -0
- aaw_core/relay/push_fcm.py +164 -0
- aaw_core/relay/server.py +646 -0
- aaw_core/transport/__init__.py +0 -0
- aaw_core/transport/base.py +225 -0
- aaw_core/transport/relay.py +470 -0
- aaw_core/transport/utils.py +51 -0
- aaw_core-0.1.0.dist-info/METADATA +138 -0
- aaw_core-0.1.0.dist-info/RECORD +45 -0
- aaw_core-0.1.0.dist-info/WHEEL +4 -0
- aaw_core-0.1.0.dist-info/entry_points.txt +2 -0
- aaw_core-0.1.0.dist-info/licenses/LICENSE +21 -0
aaw_core/__init__.py
ADDED
|
@@ -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
|
aaw_core/config.py
ADDED
|
@@ -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
|
+
)
|