wingman-ai 1.0.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.
- share/wingman/node_listener/package-lock.json +1785 -0
- share/wingman/node_listener/package.json +50 -0
- share/wingman/node_listener/src/index.ts +108 -0
- share/wingman/node_listener/src/ipc.ts +70 -0
- share/wingman/node_listener/src/messageHandler.ts +135 -0
- share/wingman/node_listener/src/socket.ts +244 -0
- share/wingman/node_listener/src/types.d.ts +13 -0
- share/wingman/node_listener/tsconfig.json +19 -0
- wingman/__init__.py +4 -0
- wingman/__main__.py +6 -0
- wingman/cli/__init__.py +5 -0
- wingman/cli/commands/__init__.py +1 -0
- wingman/cli/commands/auth.py +90 -0
- wingman/cli/commands/config.py +109 -0
- wingman/cli/commands/init.py +71 -0
- wingman/cli/commands/logs.py +84 -0
- wingman/cli/commands/start.py +111 -0
- wingman/cli/commands/status.py +84 -0
- wingman/cli/commands/stop.py +33 -0
- wingman/cli/commands/uninstall.py +113 -0
- wingman/cli/main.py +50 -0
- wingman/cli/wizard.py +356 -0
- wingman/config/__init__.py +31 -0
- wingman/config/paths.py +153 -0
- wingman/config/personality.py +155 -0
- wingman/config/registry.py +343 -0
- wingman/config/settings.py +294 -0
- wingman/core/__init__.py +16 -0
- wingman/core/agent.py +257 -0
- wingman/core/ipc_handler.py +124 -0
- wingman/core/llm/__init__.py +5 -0
- wingman/core/llm/client.py +77 -0
- wingman/core/memory/__init__.py +6 -0
- wingman/core/memory/context.py +109 -0
- wingman/core/memory/models.py +213 -0
- wingman/core/message_processor.py +277 -0
- wingman/core/policy/__init__.py +5 -0
- wingman/core/policy/evaluator.py +265 -0
- wingman/core/process_manager.py +135 -0
- wingman/core/safety/__init__.py +8 -0
- wingman/core/safety/cooldown.py +63 -0
- wingman/core/safety/quiet_hours.py +75 -0
- wingman/core/safety/rate_limiter.py +58 -0
- wingman/core/safety/triggers.py +117 -0
- wingman/core/transports/__init__.py +14 -0
- wingman/core/transports/base.py +106 -0
- wingman/core/transports/imessage/__init__.py +5 -0
- wingman/core/transports/imessage/db_listener.py +280 -0
- wingman/core/transports/imessage/sender.py +162 -0
- wingman/core/transports/imessage/transport.py +140 -0
- wingman/core/transports/whatsapp.py +180 -0
- wingman/daemon/__init__.py +5 -0
- wingman/daemon/manager.py +303 -0
- wingman/installer/__init__.py +5 -0
- wingman/installer/node_installer.py +253 -0
- wingman_ai-1.0.0.dist-info/METADATA +553 -0
- wingman_ai-1.0.0.dist-info/RECORD +60 -0
- wingman_ai-1.0.0.dist-info/WHEEL +4 -0
- wingman_ai-1.0.0.dist-info/entry_points.txt +2 -0
- wingman_ai-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""Node.js listener installer."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class NodeInstaller:
|
|
12
|
+
"""
|
|
13
|
+
Installs the Node.js WhatsApp listener.
|
|
14
|
+
|
|
15
|
+
Handles:
|
|
16
|
+
- Checking for Node.js/npm prerequisites
|
|
17
|
+
- Copying bundled source to config directory
|
|
18
|
+
- Running npm install and npm run build
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Minimum required versions
|
|
22
|
+
MIN_NODE_VERSION = (18, 0, 0)
|
|
23
|
+
MIN_NPM_VERSION = (9, 0, 0)
|
|
24
|
+
|
|
25
|
+
def __init__(self, target_dir: Path):
|
|
26
|
+
"""
|
|
27
|
+
Initialize the installer.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
target_dir: Directory to install the node_listener to
|
|
31
|
+
"""
|
|
32
|
+
self.target_dir = target_dir
|
|
33
|
+
|
|
34
|
+
def check_prerequisites(self) -> tuple[bool, list[str]]:
|
|
35
|
+
"""
|
|
36
|
+
Check if Node.js and npm are installed and meet version requirements.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Tuple of (all_ok, list of issues)
|
|
40
|
+
"""
|
|
41
|
+
issues = []
|
|
42
|
+
|
|
43
|
+
# Check Node.js
|
|
44
|
+
node_version = self._get_node_version()
|
|
45
|
+
if node_version is None:
|
|
46
|
+
issues.append("Node.js is not installed. Install from https://nodejs.org/")
|
|
47
|
+
elif node_version < self.MIN_NODE_VERSION:
|
|
48
|
+
version_str = ".".join(map(str, node_version))
|
|
49
|
+
min_str = ".".join(map(str, self.MIN_NODE_VERSION))
|
|
50
|
+
issues.append(f"Node.js {version_str} is too old. Minimum: {min_str}")
|
|
51
|
+
|
|
52
|
+
# Check npm
|
|
53
|
+
npm_version = self._get_npm_version()
|
|
54
|
+
if npm_version is None:
|
|
55
|
+
issues.append("npm is not installed")
|
|
56
|
+
elif npm_version < self.MIN_NPM_VERSION:
|
|
57
|
+
version_str = ".".join(map(str, npm_version))
|
|
58
|
+
min_str = ".".join(map(str, self.MIN_NPM_VERSION))
|
|
59
|
+
issues.append(f"npm {version_str} is too old. Minimum: {min_str}")
|
|
60
|
+
|
|
61
|
+
return len(issues) == 0, issues
|
|
62
|
+
|
|
63
|
+
def _get_node_version(self) -> tuple[int, ...] | None:
|
|
64
|
+
"""Get the installed Node.js version."""
|
|
65
|
+
try:
|
|
66
|
+
result = subprocess.run(
|
|
67
|
+
["node", "--version"], capture_output=True, text=True, timeout=10
|
|
68
|
+
)
|
|
69
|
+
if result.returncode == 0:
|
|
70
|
+
# Parse version like "v20.10.0"
|
|
71
|
+
version_str = result.stdout.strip().lstrip("v")
|
|
72
|
+
parts = version_str.split(".")
|
|
73
|
+
return tuple(int(p) for p in parts[:3])
|
|
74
|
+
except (subprocess.SubprocessError, FileNotFoundError, ValueError):
|
|
75
|
+
pass
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
def _get_npm_version(self) -> tuple[int, ...] | None:
|
|
79
|
+
"""Get the installed npm version."""
|
|
80
|
+
try:
|
|
81
|
+
result = subprocess.run(
|
|
82
|
+
["npm", "--version"], capture_output=True, text=True, timeout=10
|
|
83
|
+
)
|
|
84
|
+
if result.returncode == 0:
|
|
85
|
+
# Parse version like "10.2.3"
|
|
86
|
+
version_str = result.stdout.strip()
|
|
87
|
+
parts = version_str.split(".")
|
|
88
|
+
return tuple(int(p) for p in parts[:3])
|
|
89
|
+
except (subprocess.SubprocessError, FileNotFoundError, ValueError):
|
|
90
|
+
pass
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
def get_bundled_source(self) -> Path | None:
|
|
94
|
+
"""
|
|
95
|
+
Find the bundled node_listener source.
|
|
96
|
+
|
|
97
|
+
Checks:
|
|
98
|
+
1. Package data location (pip installed)
|
|
99
|
+
2. Development location (running from source)
|
|
100
|
+
"""
|
|
101
|
+
import importlib.resources
|
|
102
|
+
|
|
103
|
+
# Try package data location first
|
|
104
|
+
try:
|
|
105
|
+
# Python 3.9+
|
|
106
|
+
with importlib.resources.as_file(
|
|
107
|
+
importlib.resources.files("wingman").joinpath("../../../node_listener")
|
|
108
|
+
) as path:
|
|
109
|
+
if path.exists() and (path / "package.json").exists():
|
|
110
|
+
return path
|
|
111
|
+
except (TypeError, FileNotFoundError):
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
# Try shared data location (pip installed with pyproject.toml shared-data)
|
|
115
|
+
import sys
|
|
116
|
+
|
|
117
|
+
for path in [
|
|
118
|
+
Path(sys.prefix) / "share" / "wingman" / "node_listener",
|
|
119
|
+
Path.home() / ".local" / "share" / "wingman" / "node_listener",
|
|
120
|
+
]:
|
|
121
|
+
if path.exists() and (path / "package.json").exists():
|
|
122
|
+
return path
|
|
123
|
+
|
|
124
|
+
# Try development location (relative to this file)
|
|
125
|
+
dev_path = Path(__file__).parent.parent.parent.parent.parent / "node_listener"
|
|
126
|
+
if dev_path.exists() and (dev_path / "package.json").exists():
|
|
127
|
+
return dev_path
|
|
128
|
+
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
def install(self, progress_callback=None) -> bool:
|
|
132
|
+
"""
|
|
133
|
+
Install the Node.js listener.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
progress_callback: Optional callback(step, message) for progress updates
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
True if installation succeeded
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
def report(step: str, message: str):
|
|
143
|
+
logger.info(f"[{step}] {message}")
|
|
144
|
+
if progress_callback:
|
|
145
|
+
progress_callback(step, message)
|
|
146
|
+
|
|
147
|
+
# Find bundled source
|
|
148
|
+
source_dir = self.get_bundled_source()
|
|
149
|
+
if source_dir is None:
|
|
150
|
+
report("error", "Could not find bundled node_listener source")
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
report("copy", f"Copying source from {source_dir}")
|
|
154
|
+
|
|
155
|
+
# Create target directory
|
|
156
|
+
self.target_dir.mkdir(parents=True, exist_ok=True)
|
|
157
|
+
|
|
158
|
+
# Copy source files (excluding node_modules and dist)
|
|
159
|
+
try:
|
|
160
|
+
self._copy_source(source_dir, self.target_dir)
|
|
161
|
+
except Exception as e:
|
|
162
|
+
report("error", f"Failed to copy source: {e}")
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
report("npm_install", "Installing npm dependencies...")
|
|
166
|
+
|
|
167
|
+
# Run npm install
|
|
168
|
+
try:
|
|
169
|
+
result = subprocess.run(
|
|
170
|
+
["npm", "install"],
|
|
171
|
+
cwd=str(self.target_dir),
|
|
172
|
+
capture_output=True,
|
|
173
|
+
text=True,
|
|
174
|
+
timeout=300, # 5 minute timeout
|
|
175
|
+
)
|
|
176
|
+
if result.returncode != 0:
|
|
177
|
+
report("error", f"npm install failed: {result.stderr}")
|
|
178
|
+
return False
|
|
179
|
+
except subprocess.TimeoutExpired:
|
|
180
|
+
report("error", "npm install timed out")
|
|
181
|
+
return False
|
|
182
|
+
except Exception as e:
|
|
183
|
+
report("error", f"npm install failed: {e}")
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
report("npm_build", "Building TypeScript...")
|
|
187
|
+
|
|
188
|
+
# Run npm run build
|
|
189
|
+
try:
|
|
190
|
+
result = subprocess.run(
|
|
191
|
+
["npm", "run", "build"],
|
|
192
|
+
cwd=str(self.target_dir),
|
|
193
|
+
capture_output=True,
|
|
194
|
+
text=True,
|
|
195
|
+
timeout=120, # 2 minute timeout
|
|
196
|
+
)
|
|
197
|
+
if result.returncode != 0:
|
|
198
|
+
report("error", f"npm run build failed: {result.stderr}")
|
|
199
|
+
return False
|
|
200
|
+
except subprocess.TimeoutExpired:
|
|
201
|
+
report("error", "npm run build timed out")
|
|
202
|
+
return False
|
|
203
|
+
except Exception as e:
|
|
204
|
+
report("error", f"npm run build failed: {e}")
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
# Verify installation
|
|
208
|
+
dist_file = self.target_dir / "dist" / "index.js"
|
|
209
|
+
if not dist_file.exists():
|
|
210
|
+
report("error", "Build completed but dist/index.js not found")
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
report("done", "Node.js listener installed successfully")
|
|
214
|
+
return True
|
|
215
|
+
|
|
216
|
+
def _copy_source(self, source: Path, target: Path) -> None:
|
|
217
|
+
"""Copy source files, excluding node_modules and dist."""
|
|
218
|
+
# Items to exclude
|
|
219
|
+
exclude = {"node_modules", "dist", ".git", "__pycache__"}
|
|
220
|
+
|
|
221
|
+
for item in source.iterdir():
|
|
222
|
+
if item.name in exclude:
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
target_item = target / item.name
|
|
226
|
+
|
|
227
|
+
if item.is_dir():
|
|
228
|
+
if target_item.exists():
|
|
229
|
+
shutil.rmtree(target_item)
|
|
230
|
+
shutil.copytree(item, target_item, ignore=shutil.ignore_patterns(*exclude))
|
|
231
|
+
else:
|
|
232
|
+
shutil.copy2(item, target_item)
|
|
233
|
+
|
|
234
|
+
def is_installed(self) -> bool:
|
|
235
|
+
"""Check if the node_listener is already installed and built."""
|
|
236
|
+
return (
|
|
237
|
+
self.target_dir.exists()
|
|
238
|
+
and (self.target_dir / "package.json").exists()
|
|
239
|
+
and (self.target_dir / "dist" / "index.js").exists()
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def get_version_info(self) -> dict:
|
|
243
|
+
"""Get version information for diagnostics."""
|
|
244
|
+
node_version = self._get_node_version()
|
|
245
|
+
npm_version = self._get_npm_version()
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
"node_version": ".".join(map(str, node_version)) if node_version else None,
|
|
249
|
+
"npm_version": ".".join(map(str, npm_version)) if npm_version else None,
|
|
250
|
+
"target_dir": str(self.target_dir),
|
|
251
|
+
"is_installed": self.is_installed(),
|
|
252
|
+
"bundled_source": str(self.get_bundled_source()) if self.get_bundled_source() else None,
|
|
253
|
+
}
|