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.
Files changed (45) hide show
  1. aaw_core/__init__.py +7 -0
  2. aaw_core/agents/__init__.py +0 -0
  3. aaw_core/config.py +151 -0
  4. aaw_core/daemon.py +712 -0
  5. aaw_core/encryption.py +96 -0
  6. aaw_core/hooks/__init__.py +0 -0
  7. aaw_core/hooks/common.py +201 -0
  8. aaw_core/hooks/on_notification.py +39 -0
  9. aaw_core/hooks/on_post_tool.py +57 -0
  10. aaw_core/hooks/on_pre_tool.py +195 -0
  11. aaw_core/hooks/on_session_start.py +65 -0
  12. aaw_core/hooks/on_stop.py +114 -0
  13. aaw_core/hooks/on_stop_claude.py +287 -0
  14. aaw_core/hooks/on_stop_cursor.py +51 -0
  15. aaw_core/hooks/on_stop_gemini.py +14 -0
  16. aaw_core/hooks/on_stop_grok.py +206 -0
  17. aaw_core/hooks/on_user_prompt.py +43 -0
  18. aaw_core/host/__init__.py +0 -0
  19. aaw_core/host/cli.py +846 -0
  20. aaw_core/host/detect.py +449 -0
  21. aaw_core/host/fs.py +162 -0
  22. aaw_core/host/hooks_installer.py +376 -0
  23. aaw_core/host/identity.py +54 -0
  24. aaw_core/host/scripts/shell_integration.sh +58 -0
  25. aaw_core/host/service.py +209 -0
  26. aaw_core/host/session_id.py +75 -0
  27. aaw_core/host/sessions.py +388 -0
  28. aaw_core/host/state.py +97 -0
  29. aaw_core/host/supervisor.py +405 -0
  30. aaw_core/host/tmux.py +201 -0
  31. aaw_core/host/usage.py +283 -0
  32. aaw_core/relay/__init__.py +8 -0
  33. aaw_core/relay/__main__.py +44 -0
  34. aaw_core/relay/backup.py +56 -0
  35. aaw_core/relay/push_fcm.py +164 -0
  36. aaw_core/relay/server.py +646 -0
  37. aaw_core/transport/__init__.py +0 -0
  38. aaw_core/transport/base.py +225 -0
  39. aaw_core/transport/relay.py +470 -0
  40. aaw_core/transport/utils.py +51 -0
  41. aaw_core-0.1.0.dist-info/METADATA +138 -0
  42. aaw_core-0.1.0.dist-info/RECORD +45 -0
  43. aaw_core-0.1.0.dist-info/WHEEL +4 -0
  44. aaw_core-0.1.0.dist-info/entry_points.txt +2 -0
  45. 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
+ )