tweek 0.4.0__py3-none-any.whl → 0.4.2__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.
- tweek/__init__.py +1 -1
- tweek/cli_core.py +23 -6
- tweek/cli_install.py +361 -91
- tweek/cli_uninstall.py +119 -36
- tweek/config/families.yaml +13 -0
- tweek/config/models.py +31 -3
- tweek/config/patterns.yaml +126 -2
- tweek/diagnostics.py +124 -1
- tweek/hooks/break_glass.py +70 -47
- tweek/hooks/overrides.py +19 -1
- tweek/hooks/post_tool_use.py +6 -2
- tweek/hooks/pre_tool_use.py +19 -2
- tweek/hooks/wrapper_post_tool_use.py +121 -0
- tweek/hooks/wrapper_pre_tool_use.py +121 -0
- tweek/integrations/openclaw.py +70 -60
- tweek/integrations/openclaw_detection.py +140 -0
- tweek/integrations/openclaw_server.py +359 -86
- tweek/logging/security_log.py +22 -0
- tweek/memory/safety.py +7 -3
- tweek/memory/store.py +31 -10
- tweek/plugins/base.py +9 -1
- tweek/plugins/detectors/openclaw.py +31 -92
- tweek/plugins/screening/heuristic_scorer.py +12 -1
- tweek/plugins/screening/local_model_reviewer.py +9 -0
- tweek/security/language.py +2 -1
- tweek/security/llm_reviewer.py +53 -24
- tweek/security/local_model.py +21 -0
- tweek/security/model_registry.py +2 -2
- tweek/security/rate_limiter.py +99 -1
- tweek/skills/guard.py +30 -7
- {tweek-0.4.0.dist-info → tweek-0.4.2.dist-info}/METADATA +1 -1
- {tweek-0.4.0.dist-info → tweek-0.4.2.dist-info}/RECORD +37 -34
- {tweek-0.4.0.dist-info → tweek-0.4.2.dist-info}/WHEEL +0 -0
- {tweek-0.4.0.dist-info → tweek-0.4.2.dist-info}/entry_points.txt +0 -0
- {tweek-0.4.0.dist-info → tweek-0.4.2.dist-info}/licenses/LICENSE +0 -0
- {tweek-0.4.0.dist-info → tweek-0.4.2.dist-info}/licenses/NOTICE +0 -0
- {tweek-0.4.0.dist-info → tweek-0.4.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared OpenClaw detection utilities.
|
|
3
|
+
|
|
4
|
+
Centralizes OpenClaw detection logic used by both the integrations module
|
|
5
|
+
and the detector plugin to eliminate code duplication.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import socket
|
|
11
|
+
import subprocess
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# OpenClaw default paths and ports
|
|
17
|
+
OPENCLAW_DEFAULT_PORT = 18789
|
|
18
|
+
OPENCLAW_HOME = Path.home() / ".openclaw"
|
|
19
|
+
OPENCLAW_CONFIG = OPENCLAW_HOME / "openclaw.json"
|
|
20
|
+
OPENCLAW_SKILLS_DIR = OPENCLAW_HOME / "workspace" / "skills"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def check_npm_installation() -> Optional[Dict[str, str]]:
|
|
24
|
+
"""Check if openclaw is installed via npm.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Dict with 'version' and/or 'path' keys if found, None otherwise.
|
|
28
|
+
"""
|
|
29
|
+
# Try npm list -g
|
|
30
|
+
try:
|
|
31
|
+
proc = subprocess.run(
|
|
32
|
+
["npm", "list", "-g", "openclaw", "--json"],
|
|
33
|
+
capture_output=True,
|
|
34
|
+
text=True,
|
|
35
|
+
timeout=10,
|
|
36
|
+
)
|
|
37
|
+
if proc.returncode == 0:
|
|
38
|
+
data = json.loads(proc.stdout)
|
|
39
|
+
deps = data.get("dependencies", {})
|
|
40
|
+
if "openclaw" in deps:
|
|
41
|
+
return {
|
|
42
|
+
"version": deps["openclaw"].get("version", "unknown"),
|
|
43
|
+
"path": data.get("path", ""),
|
|
44
|
+
}
|
|
45
|
+
except subprocess.TimeoutExpired:
|
|
46
|
+
pass
|
|
47
|
+
except json.JSONDecodeError:
|
|
48
|
+
pass
|
|
49
|
+
except FileNotFoundError:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
# Fallback: try which/where
|
|
53
|
+
try:
|
|
54
|
+
cmd = ["which", "openclaw"] if os.name != "nt" else ["where", "openclaw"]
|
|
55
|
+
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=5)
|
|
56
|
+
if proc.returncode == 0 and proc.stdout.strip():
|
|
57
|
+
return {"path": proc.stdout.strip().split("\n")[0]}
|
|
58
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def check_running_process() -> Optional[Dict[str, Any]]:
|
|
65
|
+
"""Check if an openclaw process is running.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Dict with process info if found, None otherwise.
|
|
69
|
+
On Unix: may contain 'pid' key.
|
|
70
|
+
On Windows: contains 'running': True.
|
|
71
|
+
"""
|
|
72
|
+
try:
|
|
73
|
+
if os.name == "nt":
|
|
74
|
+
proc = subprocess.run(
|
|
75
|
+
["tasklist", "/FI", "IMAGENAME eq node.exe", "/FO", "CSV"],
|
|
76
|
+
capture_output=True,
|
|
77
|
+
text=True,
|
|
78
|
+
timeout=10,
|
|
79
|
+
)
|
|
80
|
+
if "openclaw" in proc.stdout.lower():
|
|
81
|
+
return {"running": True}
|
|
82
|
+
else:
|
|
83
|
+
proc = subprocess.run(
|
|
84
|
+
["pgrep", "-f", "openclaw"],
|
|
85
|
+
capture_output=True,
|
|
86
|
+
text=True,
|
|
87
|
+
timeout=5,
|
|
88
|
+
)
|
|
89
|
+
if proc.returncode == 0 and proc.stdout.strip():
|
|
90
|
+
pids = proc.stdout.strip().split("\n")
|
|
91
|
+
return {"pid": pids[0]}
|
|
92
|
+
|
|
93
|
+
# Also check for node process with openclaw
|
|
94
|
+
proc = subprocess.run(
|
|
95
|
+
["pgrep", "-af", "node.*openclaw"],
|
|
96
|
+
capture_output=True,
|
|
97
|
+
text=True,
|
|
98
|
+
timeout=5,
|
|
99
|
+
)
|
|
100
|
+
if proc.returncode == 0 and proc.stdout.strip():
|
|
101
|
+
return {"running": True}
|
|
102
|
+
|
|
103
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def check_gateway_active(port: int) -> bool:
|
|
110
|
+
"""Check if OpenClaw gateway is listening on port.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
port: TCP port number to check.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
True if a service is listening on the port.
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
120
|
+
sock.settimeout(1)
|
|
121
|
+
result = sock.connect_ex(("127.0.0.1", port))
|
|
122
|
+
return result == 0
|
|
123
|
+
except (socket.error, OSError):
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def read_config_port() -> Optional[int]:
|
|
128
|
+
"""Read the gateway port from openclaw.json config.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
The configured port, or None if not found.
|
|
132
|
+
"""
|
|
133
|
+
if not OPENCLAW_CONFIG.exists():
|
|
134
|
+
return None
|
|
135
|
+
try:
|
|
136
|
+
with open(OPENCLAW_CONFIG) as f:
|
|
137
|
+
config = json.load(f)
|
|
138
|
+
return config.get("gateway", {}).get("port")
|
|
139
|
+
except (json.JSONDecodeError, IOError):
|
|
140
|
+
return None
|