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.
Files changed (40) hide show
  1. {devdash_mac-0.1.3/src/devdash_mac.egg-info → devdash_mac-0.1.4}/PKG-INFO +1 -1
  2. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/pyproject.toml +1 -1
  3. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/__init__.py +1 -1
  4. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/app.py +49 -43
  5. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/base64_tool.py +1 -1
  6. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/color_tool.py +1 -1
  7. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/cron_tool.py +1 -1
  8. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/hash_tool.py +1 -1
  9. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/json_tool.py +1 -1
  10. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/jwt_tool.py +1 -1
  11. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/lorem_tool.py +1 -1
  12. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/password_tool.py +2 -1
  13. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/regex_tool.py +1 -1
  14. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/timestamp_tool.py +1 -1
  15. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/url_tool.py +1 -1
  16. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/uuid_tool.py +1 -1
  17. devdash_mac-0.1.4/src/devdash/ui/windows.py +166 -0
  18. {devdash_mac-0.1.3 → devdash_mac-0.1.4/src/devdash_mac.egg-info}/PKG-INFO +1 -1
  19. devdash_mac-0.1.3/src/devdash/ui/windows.py +0 -84
  20. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/LICENSE +0 -0
  21. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/README.md +0 -0
  22. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/setup.cfg +0 -0
  23. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/__main__.py +0 -0
  24. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/clipboard.py +0 -0
  25. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/config.py +0 -0
  26. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/plugin_loader.py +0 -0
  27. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/storage.py +0 -0
  28. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/__init__.py +0 -0
  29. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/tools/base.py +0 -0
  30. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/ui/__init__.py +0 -0
  31. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash/ui/notifications.py +0 -0
  32. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash_mac.egg-info/SOURCES.txt +0 -0
  33. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash_mac.egg-info/dependency_links.txt +0 -0
  34. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash_mac.egg-info/entry_points.txt +0 -0
  35. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash_mac.egg-info/requires.txt +0 -0
  36. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/src/devdash_mac.egg-info/top_level.txt +0 -0
  37. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/tests/test_app.py +0 -0
  38. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/tests/test_clipboard.py +0 -0
  39. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/tests/test_config.py +0 -0
  40. {devdash_mac-0.1.3 → devdash_mac-0.1.4}/tests/test_plugin_loader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devdash-mac
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Open-source macOS menubar developer utilities
5
5
  Author: DevDash Contributors
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devdash-mac"
7
- version = "0.1.3"
7
+ version = "0.1.4"
8
8
  description = "Open-source macOS menubar developer utilities"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -1,4 +1,4 @@
1
1
  """DevDash - Open-source macOS menubar developer utilities."""
2
2
 
3
- __version__ = "0.1.3"
3
+ __version__ = "0.1.4"
4
4
  __app_name__ = "DevDash"
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import threading
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._make_tool_callback(tool))
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 _make_tool_callback(self, tool: DevTool): # type: ignore[no-untyped-def]
71
- """Create a callback closure for a specific tool."""
72
-
73
- def callback(_: rumps.MenuItem) -> None:
74
- # Run in a thread so the menubar stays responsive
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
- def _detect_and_open() -> None:
85
- content = clipboard.read()
86
- if not content.strip():
87
- notify("DevDash", "Clipboard is empty")
88
- return
89
-
90
- detected = clipboard.detect_type(content)
91
- keyword_map = {
92
- clipboard.ContentType.JSON: "json",
93
- clipboard.ContentType.JWT: "jwt",
94
- clipboard.ContentType.UUID: "uuid",
95
- clipboard.ContentType.BASE64: "base64",
96
- clipboard.ContentType.UNIX_TIMESTAMP: "timestamp",
97
- clipboard.ContentType.URL: "url",
98
- clipboard.ContentType.URL_ENCODED: "url",
99
- clipboard.ContentType.HEX_COLOR: "color",
100
- clipboard.ContentType.CRON: "cron",
101
- }
102
- keyword = keyword_map.get(detected)
103
- if keyword:
104
- for tool in self._tools:
105
- if tool.keyword == keyword:
106
- show_tool_dialog(tool, input_text=content)
107
- return
108
- notify("DevDash", f"Detected: {detected.name}. No matching tool found.")
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
- rumps.alert(
115
- title=f"About {__app_name__}",
116
- message=(
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 "Encode or decode Base64 (standard and URL-safe)"
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 "Convert between HEX, RGB, HSL, and HSV color formats"
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 "Parse cron expressions to human-readable descriptions"
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 "Generate MD5, SHA-1, SHA-256, SHA-512, BLAKE2b hashes"
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 "Format, validate, or minify JSON"
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 "Decode JWT tokens (header + payload + expiry check)"
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 "Generate lorem ipsum text by words, sentences, or paragraphs"
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 "Generate secure passwords or passphrases"
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 "Test regex patterns with match highlighting"
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 "Convert between Unix timestamps and human-readable dates"
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 encode/decode and parse URL components"
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 "Generate UUID v4, v7, or ULID; validate UUIDs"
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devdash-mac
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Open-source macOS menubar developer utilities
5
5
  Author: DevDash Contributors
6
6
  License: MIT
@@ -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