tweek 0.2.0__py3-none-any.whl → 0.2.1__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.py +124 -25
- {tweek-0.2.0.dist-info → tweek-0.2.1.dist-info}/METADATA +1 -1
- {tweek-0.2.0.dist-info → tweek-0.2.1.dist-info}/RECORD +9 -8
- tweek-0.2.1.dist-info/top_level.txt +2 -0
- tweek-openclaw-plugin/node_modules/flatted/python/flatted.py +149 -0
- tweek-0.2.0.dist-info/top_level.txt +0 -1
- {tweek-0.2.0.dist-info → tweek-0.2.1.dist-info}/WHEEL +0 -0
- {tweek-0.2.0.dist-info → tweek-0.2.1.dist-info}/entry_points.txt +0 -0
- {tweek-0.2.0.dist-info → tweek-0.2.1.dist-info}/licenses/LICENSE +0 -0
tweek/__init__.py
CHANGED
tweek/cli.py
CHANGED
|
@@ -236,13 +236,13 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
|
|
|
236
236
|
else:
|
|
237
237
|
console.print()
|
|
238
238
|
console.print("[yellow]⚠ Claude Code not detected on this system[/yellow]")
|
|
239
|
-
console.print(" [
|
|
240
|
-
console.print(" [
|
|
239
|
+
console.print(" [white]Tweek hooks require Claude Code to function.[/white]")
|
|
240
|
+
console.print(" [white]https://docs.anthropic.com/en/docs/claude-code[/white]")
|
|
241
241
|
console.print()
|
|
242
242
|
if quick or not click.confirm("Continue installing hooks anyway?", default=False):
|
|
243
243
|
if not quick:
|
|
244
244
|
console.print()
|
|
245
|
-
console.print("[
|
|
245
|
+
console.print("[white]Run 'tweek install' later after installing Claude Code.[/white]")
|
|
246
246
|
return
|
|
247
247
|
console.print()
|
|
248
248
|
|
|
@@ -250,25 +250,16 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
|
|
|
250
250
|
# Step 2: Scope selection (always shown unless --global or --quick)
|
|
251
251
|
# ─────────────────────────────────────────────────────────────
|
|
252
252
|
if not install_global and not dev_test and not quick:
|
|
253
|
-
# Smart default: if in a git repo, default to project; otherwise global
|
|
254
|
-
in_git_repo = (Path.cwd() / ".git").exists()
|
|
255
|
-
default_scope = 1 if in_git_repo else 2
|
|
256
|
-
|
|
257
253
|
console.print()
|
|
258
254
|
console.print("[bold]Installation Scope[/bold]")
|
|
259
255
|
console.print()
|
|
260
|
-
console.print(" [cyan]1.[/cyan]
|
|
261
|
-
console.print(" [
|
|
262
|
-
console.print(" [cyan]2.[/cyan]
|
|
263
|
-
console.print(" [
|
|
264
|
-
console.print()
|
|
265
|
-
if in_git_repo:
|
|
266
|
-
console.print(f" [dim]Git repo detected — defaulting to project scope[/dim]")
|
|
267
|
-
else:
|
|
268
|
-
console.print(f" [dim]No git repo — defaulting to global scope[/dim]")
|
|
256
|
+
console.print(" [cyan]1.[/cyan] All projects globally (~/.claude/) [green](recommended)[/green]")
|
|
257
|
+
console.print(" [white]Protects every project on this machine[/white]")
|
|
258
|
+
console.print(" [cyan]2.[/cyan] This directory only (./.claude/)")
|
|
259
|
+
console.print(" [white]Protects only the current directory[/white]")
|
|
269
260
|
console.print()
|
|
270
|
-
scope_choice = click.prompt("Select", type=click.IntRange(1, 2), default=
|
|
271
|
-
if scope_choice ==
|
|
261
|
+
scope_choice = click.prompt("Select", type=click.IntRange(1, 2), default=1)
|
|
262
|
+
if scope_choice == 1:
|
|
272
263
|
install_global = True
|
|
273
264
|
console.print()
|
|
274
265
|
|
|
@@ -298,8 +289,8 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
|
|
|
298
289
|
with open(project_settings) as f:
|
|
299
290
|
project_config = json.load(f)
|
|
300
291
|
if _has_tweek_hooks(project_config):
|
|
301
|
-
console.print("[
|
|
302
|
-
console.print("[
|
|
292
|
+
console.print("[white]Note: Tweek is also installed in this project.[/white]")
|
|
293
|
+
console.print("[white]Project-level settings take precedence over global.[/white]")
|
|
303
294
|
console.print()
|
|
304
295
|
else:
|
|
305
296
|
# Installing per-project — check if global hooks exist
|
|
@@ -308,8 +299,8 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
|
|
|
308
299
|
with open(global_settings) as f:
|
|
309
300
|
global_config = json.load(f)
|
|
310
301
|
if _has_tweek_hooks(global_config):
|
|
311
|
-
console.print("[
|
|
312
|
-
console.print("[
|
|
302
|
+
console.print("[white]Note: Tweek is also installed globally.[/white]")
|
|
303
|
+
console.print("[white]Project-level settings will take precedence in this directory.[/white]")
|
|
313
304
|
console.print()
|
|
314
305
|
except (json.JSONDecodeError, IOError):
|
|
315
306
|
pass
|
|
@@ -804,14 +795,14 @@ def _check_python_version(console: Console, quick: bool) -> None:
|
|
|
804
795
|
resolved_system = Path(system_python3).resolve()
|
|
805
796
|
|
|
806
797
|
if resolved_install != resolved_system:
|
|
807
|
-
console.print(f"[
|
|
808
|
-
console.print(f"[
|
|
798
|
+
console.print(f"[white] Note: system python3 is {resolved_system}[/white]")
|
|
799
|
+
console.print(f"[white] Hooks will use {resolved_install} (the Python running this install)[/white]")
|
|
809
800
|
except (OSError, ValueError):
|
|
810
801
|
pass
|
|
811
802
|
else:
|
|
812
803
|
if not quick:
|
|
813
804
|
console.print("[yellow] Note: python3 not found on PATH[/yellow]")
|
|
814
|
-
console.print(f"[
|
|
805
|
+
console.print(f"[white] Hooks will use {sys.executable} directly[/white]")
|
|
815
806
|
|
|
816
807
|
|
|
817
808
|
def _configure_llm_provider(tweek_dir: Path, interactive: bool, quick: bool) -> dict:
|
|
@@ -2185,6 +2176,114 @@ def status(verbose: bool, json_out: bool):
|
|
|
2185
2176
|
print_doctor_results(checks)
|
|
2186
2177
|
|
|
2187
2178
|
|
|
2179
|
+
@main.command("upgrade")
|
|
2180
|
+
def upgrade():
|
|
2181
|
+
"""Upgrade Tweek to the latest version from PyPI.
|
|
2182
|
+
|
|
2183
|
+
Detects how Tweek was installed (uv, pipx, or pip) and runs
|
|
2184
|
+
the appropriate upgrade command.
|
|
2185
|
+
"""
|
|
2186
|
+
import subprocess
|
|
2187
|
+
|
|
2188
|
+
console.print("[cyan]Checking for updates...[/cyan]")
|
|
2189
|
+
console.print()
|
|
2190
|
+
|
|
2191
|
+
current_version = None
|
|
2192
|
+
try:
|
|
2193
|
+
from tweek import __version__
|
|
2194
|
+
current_version = __version__
|
|
2195
|
+
console.print(f" Current version: [bold]{current_version}[/bold]")
|
|
2196
|
+
except ImportError:
|
|
2197
|
+
pass
|
|
2198
|
+
|
|
2199
|
+
# Detect install method and upgrade
|
|
2200
|
+
upgraded = False
|
|
2201
|
+
|
|
2202
|
+
# Try uv first
|
|
2203
|
+
try:
|
|
2204
|
+
result = subprocess.run(
|
|
2205
|
+
["uv", "tool", "list"], capture_output=True, text=True, timeout=10
|
|
2206
|
+
)
|
|
2207
|
+
if result.returncode == 0 and "tweek" in result.stdout:
|
|
2208
|
+
console.print(" Install method: [cyan]uv[/cyan]")
|
|
2209
|
+
console.print()
|
|
2210
|
+
console.print("[white]Upgrading via uv...[/white]")
|
|
2211
|
+
proc = subprocess.run(
|
|
2212
|
+
["uv", "tool", "upgrade", "tweek"],
|
|
2213
|
+
capture_output=False, timeout=120
|
|
2214
|
+
)
|
|
2215
|
+
if proc.returncode == 0:
|
|
2216
|
+
upgraded = True
|
|
2217
|
+
else:
|
|
2218
|
+
console.print("[yellow]uv upgrade failed, trying reinstall...[/yellow]")
|
|
2219
|
+
subprocess.run(
|
|
2220
|
+
["uv", "tool", "install", "--force", "tweek"],
|
|
2221
|
+
capture_output=False, timeout=120
|
|
2222
|
+
)
|
|
2223
|
+
upgraded = True
|
|
2224
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
2225
|
+
pass
|
|
2226
|
+
|
|
2227
|
+
# Try pipx
|
|
2228
|
+
if not upgraded:
|
|
2229
|
+
try:
|
|
2230
|
+
result = subprocess.run(
|
|
2231
|
+
["pipx", "list"], capture_output=True, text=True, timeout=10
|
|
2232
|
+
)
|
|
2233
|
+
if result.returncode == 0 and "tweek" in result.stdout:
|
|
2234
|
+
console.print(" Install method: [cyan]pipx[/cyan]")
|
|
2235
|
+
console.print()
|
|
2236
|
+
console.print("[white]Upgrading via pipx...[/white]")
|
|
2237
|
+
proc = subprocess.run(
|
|
2238
|
+
["pipx", "upgrade", "tweek"],
|
|
2239
|
+
capture_output=False, timeout=120
|
|
2240
|
+
)
|
|
2241
|
+
upgraded = proc.returncode == 0
|
|
2242
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
2243
|
+
pass
|
|
2244
|
+
|
|
2245
|
+
# Try pip
|
|
2246
|
+
if not upgraded:
|
|
2247
|
+
try:
|
|
2248
|
+
result = subprocess.run(
|
|
2249
|
+
[sys.executable, "-m", "pip", "show", "tweek"],
|
|
2250
|
+
capture_output=True, text=True, timeout=10
|
|
2251
|
+
)
|
|
2252
|
+
if result.returncode == 0:
|
|
2253
|
+
console.print(" Install method: [cyan]pip[/cyan]")
|
|
2254
|
+
console.print()
|
|
2255
|
+
console.print("[white]Upgrading via pip...[/white]")
|
|
2256
|
+
proc = subprocess.run(
|
|
2257
|
+
[sys.executable, "-m", "pip", "install", "--upgrade", "tweek"],
|
|
2258
|
+
capture_output=False, timeout=120
|
|
2259
|
+
)
|
|
2260
|
+
upgraded = proc.returncode == 0
|
|
2261
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
2262
|
+
pass
|
|
2263
|
+
|
|
2264
|
+
if not upgraded:
|
|
2265
|
+
console.print("[red]Could not determine install method.[/red]")
|
|
2266
|
+
console.print("[white]Try manually:[/white]")
|
|
2267
|
+
console.print(" uv tool upgrade tweek")
|
|
2268
|
+
console.print(" pipx upgrade tweek")
|
|
2269
|
+
console.print(" pip install --upgrade tweek")
|
|
2270
|
+
return
|
|
2271
|
+
|
|
2272
|
+
# Show new version
|
|
2273
|
+
console.print()
|
|
2274
|
+
try:
|
|
2275
|
+
result = subprocess.run(
|
|
2276
|
+
["tweek", "--version"], capture_output=True, text=True, timeout=10
|
|
2277
|
+
)
|
|
2278
|
+
if result.returncode == 0:
|
|
2279
|
+
new_version = result.stdout.strip()
|
|
2280
|
+
console.print(f"[green]✓[/green] Updated to {new_version}")
|
|
2281
|
+
else:
|
|
2282
|
+
console.print("[green]✓[/green] Update complete")
|
|
2283
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
2284
|
+
console.print("[green]✓[/green] Update complete")
|
|
2285
|
+
|
|
2286
|
+
|
|
2188
2287
|
@main.command(
|
|
2189
2288
|
epilog="""\b
|
|
2190
2289
|
Examples:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
tweek/__init__.py,sha256=
|
|
1
|
+
tweek/__init__.py,sha256=mZ5RaMD3SVWt8mcinSq8KlCaNLoVqgVu-9cewcMVlt0,360
|
|
2
2
|
tweek/_keygen.py,sha256=UapwIKNSwaRWdqHoJoF3hmKuiux6aIiFGe8WVskTbI8,1286
|
|
3
3
|
tweek/audit.py,sha256=Bp4RETwdiHpT2EEi45atZa0LlJUOtALhrc3UT8MHvF8,8868
|
|
4
|
-
tweek/cli.py,sha256=
|
|
4
|
+
tweek/cli.py,sha256=J7L0xhfZ1DmzrVGwvEBTCRLYd5w5UakSGWH9DikOOSg,245768
|
|
5
5
|
tweek/cli_helpers.py,sha256=adczf-8oHsc-TDdfJqPQoG4IgWEfJmRORm2NmVWaCOw,5494
|
|
6
6
|
tweek/cli_model.py,sha256=QO6Q3iy0d-hsMLdgWwDiigqN0rjep4Ufa73VCZjVguc,12825
|
|
7
7
|
tweek/diagnostics.py,sha256=eOiI6MlfqimzUeG7Uvwhcnen_FlINejfawLun5DY59o,22472
|
|
@@ -113,9 +113,10 @@ tweek/skills/scanner.py,sha256=PaeZNnwxLTGls2O3hQaDgBhGw9jVJThPjfKCY_05_nI,27574
|
|
|
113
113
|
tweek/vault/__init__.py,sha256=L408fjdRYL8-VqLEsyyHSO9PkBDhd_2mPIbrCu53YhM,980
|
|
114
114
|
tweek/vault/cross_platform.py,sha256=D4UvX_7OpSo8iRx5sc2OUUWQIk8JHhgeFBYk1MbyIj4,8251
|
|
115
115
|
tweek/vault/keychain.py,sha256=XL18-SUj7HwuqxLXZDViuCH81--KMu68jN9Szn1aeyw,10624
|
|
116
|
-
tweek-0.2.
|
|
117
|
-
tweek-
|
|
118
|
-
tweek-0.2.
|
|
119
|
-
tweek-0.2.
|
|
120
|
-
tweek-0.2.
|
|
121
|
-
tweek-0.2.
|
|
116
|
+
tweek-0.2.1.dist-info/licenses/LICENSE,sha256=rjoDzr1vAf0bsqZglpIyekU5aewIkCk4jHZZDvVI2BE,15269
|
|
117
|
+
tweek-openclaw-plugin/node_modules/flatted/python/flatted.py,sha256=UYburBDqkySaTfSpntPCUJRxiBGcplusJM7ECX8FEgA,3860
|
|
118
|
+
tweek-0.2.1.dist-info/METADATA,sha256=7ahRw8rb7M0OV4xVZpI_5e1MMrk2KZGont3GRymkhmw,11318
|
|
119
|
+
tweek-0.2.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
120
|
+
tweek-0.2.1.dist-info/entry_points.txt,sha256=YXThD6UiF5XQXwqW33sphsvz-Bl4Zm6pm-xq-5wcCYE,1337
|
|
121
|
+
tweek-0.2.1.dist-info/top_level.txt,sha256=jtNcCxjoGXN8IBqEVL0F3LHDrZD_B0S-4XF9-Ur7Pbc,28
|
|
122
|
+
tweek-0.2.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# ISC License
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2018-2025, Andrea Giammarchi, @WebReflection
|
|
4
|
+
#
|
|
5
|
+
# Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
# purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
# copyright notice and this permission notice appear in all copies.
|
|
8
|
+
#
|
|
9
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
14
|
+
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
# PERFORMANCE OF THIS SOFTWARE.
|
|
16
|
+
|
|
17
|
+
import json as _json
|
|
18
|
+
|
|
19
|
+
class _Known:
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.key = []
|
|
22
|
+
self.value = []
|
|
23
|
+
|
|
24
|
+
class _String:
|
|
25
|
+
def __init__(self, value):
|
|
26
|
+
self.value = value
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _array_keys(value):
|
|
30
|
+
keys = []
|
|
31
|
+
i = 0
|
|
32
|
+
for _ in value:
|
|
33
|
+
keys.append(i)
|
|
34
|
+
i += 1
|
|
35
|
+
return keys
|
|
36
|
+
|
|
37
|
+
def _object_keys(value):
|
|
38
|
+
keys = []
|
|
39
|
+
for key in value:
|
|
40
|
+
keys.append(key)
|
|
41
|
+
return keys
|
|
42
|
+
|
|
43
|
+
def _is_array(value):
|
|
44
|
+
return isinstance(value, (list, tuple))
|
|
45
|
+
|
|
46
|
+
def _is_object(value):
|
|
47
|
+
return isinstance(value, dict)
|
|
48
|
+
|
|
49
|
+
def _is_string(value):
|
|
50
|
+
return isinstance(value, str)
|
|
51
|
+
|
|
52
|
+
def _index(known, input, value):
|
|
53
|
+
input.append(value)
|
|
54
|
+
index = str(len(input) - 1)
|
|
55
|
+
known.key.append(value)
|
|
56
|
+
known.value.append(index)
|
|
57
|
+
return index
|
|
58
|
+
|
|
59
|
+
def _loop(keys, input, known, output):
|
|
60
|
+
for key in keys:
|
|
61
|
+
value = output[key]
|
|
62
|
+
if isinstance(value, _String):
|
|
63
|
+
_ref(key, input[int(value.value)], input, known, output)
|
|
64
|
+
|
|
65
|
+
return output
|
|
66
|
+
|
|
67
|
+
def _ref(key, value, input, known, output):
|
|
68
|
+
if _is_array(value) and value not in known:
|
|
69
|
+
known.append(value)
|
|
70
|
+
value = _loop(_array_keys(value), input, known, value)
|
|
71
|
+
elif _is_object(value) and value not in known:
|
|
72
|
+
known.append(value)
|
|
73
|
+
value = _loop(_object_keys(value), input, known, value)
|
|
74
|
+
|
|
75
|
+
output[key] = value
|
|
76
|
+
|
|
77
|
+
def _relate(known, input, value):
|
|
78
|
+
if _is_string(value) or _is_array(value) or _is_object(value):
|
|
79
|
+
try:
|
|
80
|
+
return known.value[known.key.index(value)]
|
|
81
|
+
except:
|
|
82
|
+
return _index(known, input, value)
|
|
83
|
+
|
|
84
|
+
return value
|
|
85
|
+
|
|
86
|
+
def _transform(known, input, value):
|
|
87
|
+
if _is_array(value):
|
|
88
|
+
output = []
|
|
89
|
+
for val in value:
|
|
90
|
+
output.append(_relate(known, input, val))
|
|
91
|
+
return output
|
|
92
|
+
|
|
93
|
+
if _is_object(value):
|
|
94
|
+
obj = {}
|
|
95
|
+
for key in value:
|
|
96
|
+
obj[key] = _relate(known, input, value[key])
|
|
97
|
+
return obj
|
|
98
|
+
|
|
99
|
+
return value
|
|
100
|
+
|
|
101
|
+
def _wrap(value):
|
|
102
|
+
if _is_string(value):
|
|
103
|
+
return _String(value)
|
|
104
|
+
|
|
105
|
+
if _is_array(value):
|
|
106
|
+
i = 0
|
|
107
|
+
for val in value:
|
|
108
|
+
value[i] = _wrap(val)
|
|
109
|
+
i += 1
|
|
110
|
+
|
|
111
|
+
elif _is_object(value):
|
|
112
|
+
for key in value:
|
|
113
|
+
value[key] = _wrap(value[key])
|
|
114
|
+
|
|
115
|
+
return value
|
|
116
|
+
|
|
117
|
+
def parse(value, *args, **kwargs):
|
|
118
|
+
json = _json.loads(value, *args, **kwargs)
|
|
119
|
+
wrapped = []
|
|
120
|
+
for value in json:
|
|
121
|
+
wrapped.append(_wrap(value))
|
|
122
|
+
|
|
123
|
+
input = []
|
|
124
|
+
for value in wrapped:
|
|
125
|
+
if isinstance(value, _String):
|
|
126
|
+
input.append(value.value)
|
|
127
|
+
else:
|
|
128
|
+
input.append(value)
|
|
129
|
+
|
|
130
|
+
value = input[0]
|
|
131
|
+
|
|
132
|
+
if _is_array(value):
|
|
133
|
+
return _loop(_array_keys(value), input, [value], value)
|
|
134
|
+
|
|
135
|
+
if _is_object(value):
|
|
136
|
+
return _loop(_object_keys(value), input, [value], value)
|
|
137
|
+
|
|
138
|
+
return value
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def stringify(value, *args, **kwargs):
|
|
142
|
+
known = _Known()
|
|
143
|
+
input = []
|
|
144
|
+
output = []
|
|
145
|
+
i = int(_index(known, input, value))
|
|
146
|
+
while i < len(input):
|
|
147
|
+
output.append(_transform(known, input, input[i]))
|
|
148
|
+
i += 1
|
|
149
|
+
return _json.dumps(output, *args, **kwargs)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
tweek
|
|
File without changes
|
|
File without changes
|
|
File without changes
|