devdash-mac 0.1.3__tar.gz → 0.1.4__tar.gz
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.
- {devdash_mac-0.1.3/src/devdash_mac.egg-info → devdash_mac-0.1.4}/PKG-INFO +1 -1
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/pyproject.toml +1 -1
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/__init__.py +1 -1
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/app.py +49 -43
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/base64_tool.py +1 -1
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/color_tool.py +1 -1
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/cron_tool.py +1 -1
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/hash_tool.py +1 -1
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/json_tool.py +1 -1
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/jwt_tool.py +1 -1
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/lorem_tool.py +1 -1
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/password_tool.py +2 -1
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/regex_tool.py +1 -1
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/timestamp_tool.py +1 -1
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/url_tool.py +1 -1
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/uuid_tool.py +1 -1
- devdash_mac-0.1.4/src/devdash/ui/windows.py +166 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4/src/devdash_mac.egg-info}/PKG-INFO +1 -1
- devdash_mac-0.1.3/src/devdash/ui/windows.py +0 -84
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/LICENSE +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/README.md +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/setup.cfg +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/__main__.py +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/clipboard.py +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/config.py +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/plugin_loader.py +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/storage.py +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/__init__.py +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/base.py +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/ui/__init__.py +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/ui/notifications.py +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash_mac.egg-info/SOURCES.txt +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash_mac.egg-info/dependency_links.txt +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash_mac.egg-info/entry_points.txt +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash_mac.egg-info/requires.txt +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash_mac.egg-info/top_level.txt +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/tests/test_app.py +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/tests/test_clipboard.py +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/tests/test_config.py +0 -0
- {devdash_mac-0.1.3 → devdash_mac-0.1.4}/tests/test_plugin_loader.py +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import subprocess
|
|
6
6
|
|
|
7
7
|
import rumps
|
|
8
8
|
|
|
@@ -33,6 +33,7 @@ class DevDashApp(rumps.App):
|
|
|
33
33
|
def __init__(self) -> None:
|
|
34
34
|
super().__init__(name=__app_name__, title="\U0001f527", quit_button=None)
|
|
35
35
|
self._tools: list[DevTool] = discover_tools()
|
|
36
|
+
self._tool_map: dict[str, DevTool] = {}
|
|
36
37
|
self._last_clipboard: str = ""
|
|
37
38
|
self._build_menu()
|
|
38
39
|
config = load_config()
|
|
@@ -55,7 +56,8 @@ class DevDashApp(rumps.App):
|
|
|
55
56
|
if current_category:
|
|
56
57
|
menu_items.append(None)
|
|
57
58
|
current_category = tool.category
|
|
58
|
-
item = rumps.MenuItem(tool.name, callback=self.
|
|
59
|
+
item = rumps.MenuItem(tool.name, callback=self._on_tool_click)
|
|
60
|
+
self._tool_map[tool.name] = tool
|
|
59
61
|
menu_items.append(item)
|
|
60
62
|
|
|
61
63
|
menu_items.append(None)
|
|
@@ -67,53 +69,46 @@ class DevDashApp(rumps.App):
|
|
|
67
69
|
|
|
68
70
|
self.menu = menu_items
|
|
69
71
|
|
|
70
|
-
def
|
|
71
|
-
"""
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
threading.Thread(
|
|
76
|
-
target=show_tool_dialog, args=(tool,), daemon=True
|
|
77
|
-
).start()
|
|
78
|
-
|
|
79
|
-
return callback
|
|
72
|
+
def _on_tool_click(self, sender: rumps.MenuItem) -> None:
|
|
73
|
+
"""Handle tool menu item click."""
|
|
74
|
+
tool = self._tool_map.get(sender.title)
|
|
75
|
+
if tool:
|
|
76
|
+
show_tool_dialog(tool)
|
|
80
77
|
|
|
81
78
|
def _on_auto_detect(self, _: rumps.MenuItem) -> None:
|
|
82
79
|
"""Read clipboard, detect content type, open matching tool."""
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
threading.Thread(target=_detect_and_open, daemon=True).start()
|
|
80
|
+
content = clipboard.read()
|
|
81
|
+
if not content.strip():
|
|
82
|
+
_osascript_alert("DevDash", "Clipboard is empty")
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
detected = clipboard.detect_type(content)
|
|
86
|
+
keyword_map = {
|
|
87
|
+
clipboard.ContentType.JSON: "json",
|
|
88
|
+
clipboard.ContentType.JWT: "jwt",
|
|
89
|
+
clipboard.ContentType.UUID: "uuid",
|
|
90
|
+
clipboard.ContentType.BASE64: "base64",
|
|
91
|
+
clipboard.ContentType.UNIX_TIMESTAMP: "timestamp",
|
|
92
|
+
clipboard.ContentType.URL: "url",
|
|
93
|
+
clipboard.ContentType.URL_ENCODED: "url",
|
|
94
|
+
clipboard.ContentType.HEX_COLOR: "color",
|
|
95
|
+
clipboard.ContentType.CRON: "cron",
|
|
96
|
+
}
|
|
97
|
+
keyword = keyword_map.get(detected)
|
|
98
|
+
if keyword:
|
|
99
|
+
for tool in self._tools:
|
|
100
|
+
if tool.keyword == keyword:
|
|
101
|
+
show_tool_dialog(tool, input_text=content)
|
|
102
|
+
return
|
|
103
|
+
_osascript_alert(
|
|
104
|
+
"DevDash", f"Detected: {detected.name}. No matching tool found."
|
|
105
|
+
)
|
|
111
106
|
|
|
112
107
|
def _on_about(self, _: rumps.MenuItem) -> None:
|
|
113
108
|
"""Show about dialog."""
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
109
|
+
_osascript_alert(
|
|
110
|
+
f"About {__app_name__}",
|
|
111
|
+
(
|
|
117
112
|
f"{__app_name__} v{__version__}\n\n"
|
|
118
113
|
"Open-source macOS menubar developer utilities.\n"
|
|
119
114
|
"https://github.com/vamsi876/devdash"
|
|
@@ -147,6 +142,17 @@ class DevDashApp(rumps.App):
|
|
|
147
142
|
_watch_clipboard.start() # type: ignore[attr-defined]
|
|
148
143
|
|
|
149
144
|
|
|
145
|
+
def _osascript_alert(title: str, message: str) -> None:
|
|
146
|
+
"""Show a simple alert via osascript."""
|
|
147
|
+
t = title.replace("\\", "\\\\").replace('"', '\\"')
|
|
148
|
+
m = message.replace("\\", "\\\\").replace('"', '\\"')
|
|
149
|
+
subprocess.run(
|
|
150
|
+
["osascript", "-e", f'display alert "{t}" message "{m}"'],
|
|
151
|
+
capture_output=True,
|
|
152
|
+
timeout=30,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
150
156
|
def main() -> None:
|
|
151
157
|
"""Entry point for the application."""
|
|
152
158
|
app = DevDashApp()
|
|
@@ -32,7 +32,7 @@ class Base64Tool(DevTool):
|
|
|
32
32
|
|
|
33
33
|
@property
|
|
34
34
|
def description(self) -> str:
|
|
35
|
-
return "
|
|
35
|
+
return "Enter text to encode, or paste Base64 to decode"
|
|
36
36
|
|
|
37
37
|
def process(self, input_text: str, **kwargs: object) -> str:
|
|
38
38
|
if not input_text.strip():
|
|
@@ -25,7 +25,7 @@ class ColorTool(DevTool):
|
|
|
25
25
|
|
|
26
26
|
@property
|
|
27
27
|
def description(self) -> str:
|
|
28
|
-
return "
|
|
28
|
+
return "Enter a color: #hex, rgb(r,g,b), or hsl(h,s,l)"
|
|
29
29
|
|
|
30
30
|
def process(self, input_text: str, **kwargs: object) -> str:
|
|
31
31
|
if not input_text.strip():
|
|
@@ -33,7 +33,7 @@ class CronTool(DevTool):
|
|
|
33
33
|
|
|
34
34
|
@property
|
|
35
35
|
def description(self) -> str:
|
|
36
|
-
return "
|
|
36
|
+
return "Enter a cron expression (e.g. '*/5 * * * *') or leave empty for presets"
|
|
37
37
|
|
|
38
38
|
def process(self, input_text: str, **kwargs: object) -> str:
|
|
39
39
|
if not input_text.strip():
|
|
@@ -23,7 +23,7 @@ class HashTool(DevTool):
|
|
|
23
23
|
|
|
24
24
|
@property
|
|
25
25
|
def description(self) -> str:
|
|
26
|
-
return "
|
|
26
|
+
return "Enter text to generate MD5, SHA-256, and other hashes"
|
|
27
27
|
|
|
28
28
|
def process(self, input_text: str, **kwargs: object) -> str:
|
|
29
29
|
if not input_text.strip():
|
|
@@ -20,7 +20,7 @@ class JsonTool(DevTool):
|
|
|
20
20
|
|
|
21
21
|
@property
|
|
22
22
|
def description(self) -> str:
|
|
23
|
-
return "
|
|
23
|
+
return "Paste JSON to format and pretty-print"
|
|
24
24
|
|
|
25
25
|
def process(self, input_text: str, **kwargs: object) -> str:
|
|
26
26
|
if not input_text.strip():
|
|
@@ -23,7 +23,7 @@ class JwtTool(DevTool):
|
|
|
23
23
|
|
|
24
24
|
@property
|
|
25
25
|
def description(self) -> str:
|
|
26
|
-
return "
|
|
26
|
+
return "Paste a JWT token to decode header, payload, and expiry"
|
|
27
27
|
|
|
28
28
|
def process(self, input_text: str, **kwargs: object) -> str:
|
|
29
29
|
if not input_text.strip():
|
|
@@ -44,7 +44,7 @@ class LoremTool(DevTool):
|
|
|
44
44
|
|
|
45
45
|
@property
|
|
46
46
|
def description(self) -> str:
|
|
47
|
-
return "
|
|
47
|
+
return "Enter amount, e.g. '5 words', '3 sentences', or '2 paragraphs'"
|
|
48
48
|
|
|
49
49
|
def process(self, input_text: str, **kwargs: object) -> str:
|
|
50
50
|
text = input_text.strip().lower()
|
|
@@ -305,7 +305,7 @@ class PasswordTool(DevTool):
|
|
|
305
305
|
|
|
306
306
|
@property
|
|
307
307
|
def description(self) -> str:
|
|
308
|
-
return "
|
|
308
|
+
return "Enter password length (8-128) or 'passphrase'"
|
|
309
309
|
|
|
310
310
|
def process(self, input_text: str, **kwargs: object) -> str:
|
|
311
311
|
text = input_text.strip().lower()
|
|
@@ -346,6 +346,7 @@ class PasswordTool(DevTool):
|
|
|
346
346
|
for _ in range(count):
|
|
347
347
|
password = "".join(secrets.choice(charset) for _ in range(length))
|
|
348
348
|
entropy = self._entropy(length, len(charset))
|
|
349
|
+
entropy = self._entropy(length, len(charset))
|
|
349
350
|
results.append(f"{password} (entropy: {entropy:.0f} bits)")
|
|
350
351
|
|
|
351
352
|
return "\n".join(results)
|
|
@@ -28,7 +28,7 @@ class RegexTool(DevTool):
|
|
|
28
28
|
|
|
29
29
|
@property
|
|
30
30
|
def description(self) -> str:
|
|
31
|
-
return "
|
|
31
|
+
return "Enter pattern and test string separated by ---"
|
|
32
32
|
|
|
33
33
|
def process(self, input_text: str, **kwargs: object) -> str:
|
|
34
34
|
# Check kwargs first
|
|
@@ -61,7 +61,7 @@ class TimestampTool(DevTool):
|
|
|
61
61
|
|
|
62
62
|
@property
|
|
63
63
|
def description(self) -> str:
|
|
64
|
-
return "
|
|
64
|
+
return "Enter a Unix timestamp or date. Leave empty for current time."
|
|
65
65
|
|
|
66
66
|
def process(self, input_text: str, **kwargs: object) -> str:
|
|
67
67
|
if not input_text.strip():
|
|
@@ -20,7 +20,7 @@ class UrlTool(DevTool):
|
|
|
20
20
|
|
|
21
21
|
@property
|
|
22
22
|
def description(self) -> str:
|
|
23
|
-
return "URL
|
|
23
|
+
return "Paste a URL to parse, or enter text to URL-encode"
|
|
24
24
|
|
|
25
25
|
def process(self, input_text: str, **kwargs: object) -> str:
|
|
26
26
|
if not input_text.strip():
|
|
@@ -79,7 +79,7 @@ class UuidTool(DevTool):
|
|
|
79
79
|
|
|
80
80
|
@property
|
|
81
81
|
def description(self) -> str:
|
|
82
|
-
return "
|
|
82
|
+
return "Enter 'v4', 'v7', or 'ulid' to generate. Paste a UUID to validate."
|
|
83
83
|
|
|
84
84
|
def process(self, input_text: str, **kwargs: object) -> str:
|
|
85
85
|
text = input_text.strip()
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Custom dialog wrappers using osascript for reliable macOS dialogs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from devdash import clipboard
|
|
11
|
+
from devdash.ui.notifications import notify_copied
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from devdash.tools.base import DevTool
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# Metadata patterns to strip when copying output
|
|
19
|
+
_METADATA_RE = re.compile(
|
|
20
|
+
r"\s*\(entropy:.*?\)" # password entropy
|
|
21
|
+
r"|\n\nOriginal bytes:.*" # base64 byte counts
|
|
22
|
+
r"|\n\nEncoded bytes:.*"
|
|
23
|
+
r"|\n\n\[Auto-detected.*?\]" # base64 auto-detect label
|
|
24
|
+
r"|\n\nWarning:.*", # JWT warning
|
|
25
|
+
re.DOTALL,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _escape(text: str) -> str:
|
|
30
|
+
"""Escape text for use inside AppleScript strings."""
|
|
31
|
+
return (
|
|
32
|
+
text.replace("\\", "\\\\")
|
|
33
|
+
.replace('"', '\\"')
|
|
34
|
+
.replace("\n", "\\n")
|
|
35
|
+
.replace("\r", "")
|
|
36
|
+
.replace("\t", "\\t")
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _input_dialog(title: str, message: str, default: str = "") -> str | None:
|
|
41
|
+
"""Show a native macOS input dialog via osascript.
|
|
42
|
+
|
|
43
|
+
Returns the entered text, or None if cancelled.
|
|
44
|
+
"""
|
|
45
|
+
script = (
|
|
46
|
+
f'display dialog "{_escape(message)}" '
|
|
47
|
+
f'default answer "{_escape(default)}" '
|
|
48
|
+
f'with title "{_escape(title)}" '
|
|
49
|
+
f'buttons {{"Cancel", "Process"}} default button "Process"'
|
|
50
|
+
"\n"
|
|
51
|
+
"return text returned of result"
|
|
52
|
+
)
|
|
53
|
+
try:
|
|
54
|
+
r = subprocess.run(
|
|
55
|
+
["osascript", "-e", script],
|
|
56
|
+
capture_output=True,
|
|
57
|
+
text=True,
|
|
58
|
+
timeout=300,
|
|
59
|
+
)
|
|
60
|
+
if r.returncode == 0:
|
|
61
|
+
return r.stdout.strip()
|
|
62
|
+
except subprocess.TimeoutExpired:
|
|
63
|
+
logger.warning("Input dialog timed out for %s", title)
|
|
64
|
+
except Exception:
|
|
65
|
+
logger.exception("Failed to show input dialog")
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _output_dialog(title: str, result: str) -> bool:
|
|
70
|
+
"""Show a native macOS output dialog via osascript.
|
|
71
|
+
|
|
72
|
+
Returns True if the user clicked 'Copy to Clipboard'.
|
|
73
|
+
"""
|
|
74
|
+
# Truncate very long results for the dialog display
|
|
75
|
+
display = result if len(result) <= 1000 else result[:997] + "..."
|
|
76
|
+
script = (
|
|
77
|
+
f'display dialog "{_escape(display)}" '
|
|
78
|
+
f'with title "{_escape(title)}" '
|
|
79
|
+
f'buttons {{"Close", "Copy to Clipboard"}} '
|
|
80
|
+
f'default button "Copy to Clipboard"'
|
|
81
|
+
)
|
|
82
|
+
try:
|
|
83
|
+
r = subprocess.run(
|
|
84
|
+
["osascript", "-e", script],
|
|
85
|
+
capture_output=True,
|
|
86
|
+
text=True,
|
|
87
|
+
timeout=300,
|
|
88
|
+
)
|
|
89
|
+
if r.returncode == 0:
|
|
90
|
+
return "Copy to Clipboard" in r.stdout
|
|
91
|
+
except Exception:
|
|
92
|
+
logger.exception("Failed to show output dialog")
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _error_dialog(title: str, message: str) -> None:
|
|
97
|
+
"""Show a native macOS error alert via osascript."""
|
|
98
|
+
script = (
|
|
99
|
+
f'display alert "{_escape(title)}" '
|
|
100
|
+
f'message "{_escape(message)}" as critical'
|
|
101
|
+
)
|
|
102
|
+
try:
|
|
103
|
+
subprocess.run(
|
|
104
|
+
["osascript", "-e", script],
|
|
105
|
+
capture_output=True,
|
|
106
|
+
timeout=10,
|
|
107
|
+
)
|
|
108
|
+
except Exception:
|
|
109
|
+
logger.exception("Failed to show error dialog")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _clean_for_copy(result: str) -> str:
|
|
113
|
+
"""Strip display-only metadata from result before copying."""
|
|
114
|
+
return _METADATA_RE.sub("", result).strip()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def show_tool_dialog(tool: DevTool, input_text: str = "") -> None:
|
|
118
|
+
"""Show input window, process with tool, show output, offer clipboard copy."""
|
|
119
|
+
text = _input_dialog(
|
|
120
|
+
title=tool.name,
|
|
121
|
+
message=tool.description or f"Enter input for {tool.name}",
|
|
122
|
+
default=input_text,
|
|
123
|
+
)
|
|
124
|
+
if text is None:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
error = tool.validate(text)
|
|
128
|
+
if error:
|
|
129
|
+
_error_dialog("DevDash Error", error)
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
result = tool.process(text)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
_error_dialog("DevDash Error", str(e))
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
if _output_dialog(f"{tool.name} - Result", result):
|
|
139
|
+
clipboard.write(_clean_for_copy(result))
|
|
140
|
+
notify_copied()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def show_multi_input_dialog(
|
|
144
|
+
tool: DevTool,
|
|
145
|
+
fields: list[tuple[str, str]],
|
|
146
|
+
) -> list[str] | None:
|
|
147
|
+
"""Show sequential input dialogs for tools needing multiple inputs.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
tool: The tool instance.
|
|
151
|
+
fields: List of (label, default_value) tuples.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
List of input values, or None if cancelled.
|
|
155
|
+
"""
|
|
156
|
+
values: list[str] = []
|
|
157
|
+
for label, default in fields:
|
|
158
|
+
text = _input_dialog(
|
|
159
|
+
title=tool.name,
|
|
160
|
+
message=label,
|
|
161
|
+
default=default,
|
|
162
|
+
)
|
|
163
|
+
if text is None:
|
|
164
|
+
return None
|
|
165
|
+
values.append(text)
|
|
166
|
+
return values
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"""Custom rumps.Window wrappers for consistent tool UI."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
|
-
|
|
7
|
-
import rumps
|
|
8
|
-
|
|
9
|
-
from devdash import clipboard
|
|
10
|
-
from devdash.ui.notifications import notify_copied, notify_error
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from devdash.tools.base import DevTool
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def show_tool_dialog(tool: DevTool, input_text: str = "") -> None:
|
|
17
|
-
"""Show input window, process with tool, show output, offer clipboard copy."""
|
|
18
|
-
window = rumps.Window(
|
|
19
|
-
message=tool.description or f"Enter input for {tool.name}",
|
|
20
|
-
title=tool.name,
|
|
21
|
-
default_text=input_text,
|
|
22
|
-
ok="Process",
|
|
23
|
-
cancel="Cancel",
|
|
24
|
-
dimensions=(320, 200),
|
|
25
|
-
)
|
|
26
|
-
response = window.run()
|
|
27
|
-
if not response.clicked:
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
text = response.text
|
|
31
|
-
error = tool.validate(text)
|
|
32
|
-
if error:
|
|
33
|
-
notify_error(error)
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
try:
|
|
37
|
-
result = tool.process(text)
|
|
38
|
-
except Exception as e:
|
|
39
|
-
notify_error(f"Error: {e}")
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
# Show output window
|
|
43
|
-
output_window = rumps.Window(
|
|
44
|
-
message="Result (click OK to copy to clipboard):",
|
|
45
|
-
title=f"{tool.name} - Output",
|
|
46
|
-
default_text=result,
|
|
47
|
-
ok="Copy to Clipboard",
|
|
48
|
-
cancel="Close",
|
|
49
|
-
dimensions=(320, 200),
|
|
50
|
-
)
|
|
51
|
-
out_response = output_window.run()
|
|
52
|
-
if out_response.clicked:
|
|
53
|
-
clipboard.write(result)
|
|
54
|
-
notify_copied()
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def show_multi_input_dialog(
|
|
58
|
-
tool: DevTool,
|
|
59
|
-
fields: list[tuple[str, str]],
|
|
60
|
-
) -> list[str] | None:
|
|
61
|
-
"""Show sequential input dialogs for tools needing multiple inputs.
|
|
62
|
-
|
|
63
|
-
Args:
|
|
64
|
-
tool: The tool instance.
|
|
65
|
-
fields: List of (label, default_value) tuples.
|
|
66
|
-
|
|
67
|
-
Returns:
|
|
68
|
-
List of input values, or None if cancelled.
|
|
69
|
-
"""
|
|
70
|
-
values: list[str] = []
|
|
71
|
-
for label, default in fields:
|
|
72
|
-
window = rumps.Window(
|
|
73
|
-
message=label,
|
|
74
|
-
title=tool.name,
|
|
75
|
-
default_text=default,
|
|
76
|
-
ok="Next" if len(values) < len(fields) - 1 else "Process",
|
|
77
|
-
cancel="Cancel",
|
|
78
|
-
dimensions=(320, 150),
|
|
79
|
-
)
|
|
80
|
-
response = window.run()
|
|
81
|
-
if not response.clicked:
|
|
82
|
-
return None
|
|
83
|
-
values.append(response.text)
|
|
84
|
-
return values
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|