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.
Files changed (60) hide show
  1. share/wingman/node_listener/package-lock.json +1785 -0
  2. share/wingman/node_listener/package.json +50 -0
  3. share/wingman/node_listener/src/index.ts +108 -0
  4. share/wingman/node_listener/src/ipc.ts +70 -0
  5. share/wingman/node_listener/src/messageHandler.ts +135 -0
  6. share/wingman/node_listener/src/socket.ts +244 -0
  7. share/wingman/node_listener/src/types.d.ts +13 -0
  8. share/wingman/node_listener/tsconfig.json +19 -0
  9. wingman/__init__.py +4 -0
  10. wingman/__main__.py +6 -0
  11. wingman/cli/__init__.py +5 -0
  12. wingman/cli/commands/__init__.py +1 -0
  13. wingman/cli/commands/auth.py +90 -0
  14. wingman/cli/commands/config.py +109 -0
  15. wingman/cli/commands/init.py +71 -0
  16. wingman/cli/commands/logs.py +84 -0
  17. wingman/cli/commands/start.py +111 -0
  18. wingman/cli/commands/status.py +84 -0
  19. wingman/cli/commands/stop.py +33 -0
  20. wingman/cli/commands/uninstall.py +113 -0
  21. wingman/cli/main.py +50 -0
  22. wingman/cli/wizard.py +356 -0
  23. wingman/config/__init__.py +31 -0
  24. wingman/config/paths.py +153 -0
  25. wingman/config/personality.py +155 -0
  26. wingman/config/registry.py +343 -0
  27. wingman/config/settings.py +294 -0
  28. wingman/core/__init__.py +16 -0
  29. wingman/core/agent.py +257 -0
  30. wingman/core/ipc_handler.py +124 -0
  31. wingman/core/llm/__init__.py +5 -0
  32. wingman/core/llm/client.py +77 -0
  33. wingman/core/memory/__init__.py +6 -0
  34. wingman/core/memory/context.py +109 -0
  35. wingman/core/memory/models.py +213 -0
  36. wingman/core/message_processor.py +277 -0
  37. wingman/core/policy/__init__.py +5 -0
  38. wingman/core/policy/evaluator.py +265 -0
  39. wingman/core/process_manager.py +135 -0
  40. wingman/core/safety/__init__.py +8 -0
  41. wingman/core/safety/cooldown.py +63 -0
  42. wingman/core/safety/quiet_hours.py +75 -0
  43. wingman/core/safety/rate_limiter.py +58 -0
  44. wingman/core/safety/triggers.py +117 -0
  45. wingman/core/transports/__init__.py +14 -0
  46. wingman/core/transports/base.py +106 -0
  47. wingman/core/transports/imessage/__init__.py +5 -0
  48. wingman/core/transports/imessage/db_listener.py +280 -0
  49. wingman/core/transports/imessage/sender.py +162 -0
  50. wingman/core/transports/imessage/transport.py +140 -0
  51. wingman/core/transports/whatsapp.py +180 -0
  52. wingman/daemon/__init__.py +5 -0
  53. wingman/daemon/manager.py +303 -0
  54. wingman/installer/__init__.py +5 -0
  55. wingman/installer/node_installer.py +253 -0
  56. wingman_ai-1.0.0.dist-info/METADATA +553 -0
  57. wingman_ai-1.0.0.dist-info/RECORD +60 -0
  58. wingman_ai-1.0.0.dist-info/WHEEL +4 -0
  59. wingman_ai-1.0.0.dist-info/entry_points.txt +2 -0
  60. 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
+ }